diff options
author | Ulrich Spörlein <uqs@FreeBSD.org> | 2012-10-18 09:55:16 +0000 |
---|---|---|
committer | Ulrich Spörlein <uqs@FreeBSD.org> | 2012-10-18 09:55:16 +0000 |
commit | 211d2d512b87f4d2be8961b75432e4f0d5206c9a (patch) | |
tree | 314af9fcc18a8f8023bbb995dd9ef62bad563089 |
Vendor import of mdocml 1.12.1vendor/mandoc/1.12.1
It is a suite of ISC licensed tools to compile and render mdoc/man pages and
will replace groff for formatting manpages in the base system.
http://mdocml.bsd.lv/
Notes
Notes:
svn path=/vendor/mdocml/dist/; revision=241675
svn path=/vendor/mdocml/1.12.1/; revision=241676; tag=vendor/mandoc/1.12.1
-rw-r--r-- | Makefile | 645 | ||||
-rw-r--r-- | TODO | 372 | ||||
-rw-r--r-- | apropos.1 | 328 | ||||
-rw-r--r-- | apropos.c | 239 | ||||
-rw-r--r-- | apropos_db.c | 876 | ||||
-rw-r--r-- | apropos_db.h | 73 | ||||
-rw-r--r-- | arch.c | 39 | ||||
-rw-r--r-- | arch.in | 111 | ||||
-rw-r--r-- | att.c | 39 | ||||
-rw-r--r-- | att.in | 40 | ||||
-rw-r--r-- | catman.8 | 111 | ||||
-rw-r--r-- | catman.c | 511 | ||||
-rw-r--r-- | cgi.c | 1203 | ||||
-rw-r--r-- | chars.c | 167 | ||||
-rw-r--r-- | chars.in | 397 | ||||
-rw-r--r-- | compat_fgetln.c | 93 | ||||
-rw-r--r-- | compat_getsubopt.c | 104 | ||||
-rw-r--r-- | compat_strlcat.c | 67 | ||||
-rw-r--r-- | compat_strlcpy.c | 63 | ||||
-rw-r--r-- | config.h.post | 42 | ||||
-rw-r--r-- | config.h.pre | 8 | ||||
-rw-r--r-- | demandoc.1 | 109 | ||||
-rw-r--r-- | demandoc.c | 257 | ||||
-rw-r--r-- | eqn.7 | 280 | ||||
-rw-r--r-- | eqn.c | 949 | ||||
-rw-r--r-- | eqn_html.c | 81 | ||||
-rw-r--r-- | eqn_term.c | 76 | ||||
-rw-r--r-- | example.style.css | 110 | ||||
-rw-r--r-- | external.png | bin | 0 -> 165 bytes | |||
-rw-r--r-- | html.c | 699 | ||||
-rw-r--r-- | html.h | 164 | ||||
-rw-r--r-- | index.css | 48 | ||||
-rw-r--r-- | index.sgml | 364 | ||||
-rw-r--r-- | lib.c | 39 | ||||
-rw-r--r-- | lib.in | 99 | ||||
-rw-r--r-- | libman.h | 85 | ||||
-rw-r--r-- | libmandoc.h | 92 | ||||
-rw-r--r-- | libmdoc.h | 141 | ||||
-rw-r--r-- | libroff.h | 84 | ||||
-rw-r--r-- | main.c | 401 | ||||
-rw-r--r-- | main.h | 61 | ||||
-rw-r--r-- | man-cgi.css | 13 | ||||
-rw-r--r-- | man.7 | 913 | ||||
-rw-r--r-- | man.c | 690 | ||||
-rw-r--r-- | man.cgi.7 | 123 | ||||
-rw-r--r-- | man.h | 113 | ||||
-rw-r--r-- | man_hash.c | 107 | ||||
-rw-r--r-- | man_html.c | 688 | ||||
-rw-r--r-- | man_macro.c | 484 | ||||
-rw-r--r-- | man_term.c | 1117 | ||||
-rw-r--r-- | man_validate.c | 550 | ||||
-rw-r--r-- | mandoc.1 | 669 | ||||
-rw-r--r-- | mandoc.3 | 600 | ||||
-rw-r--r-- | mandoc.c | 735 | ||||
-rw-r--r-- | mandoc.h | 432 | ||||
-rw-r--r-- | mandoc_char.7 | 743 | ||||
-rw-r--r-- | mandocdb.8 | 293 | ||||
-rw-r--r-- | mandocdb.c | 1909 | ||||
-rw-r--r-- | mandocdb.h | 62 | ||||
-rw-r--r-- | manpath.c | 225 | ||||
-rw-r--r-- | manpath.h | 38 | ||||
-rw-r--r-- | mdoc.7 | 3172 | ||||
-rw-r--r-- | mdoc.c | 987 | ||||
-rw-r--r-- | mdoc.h | 392 | ||||
-rw-r--r-- | mdoc_argv.c | 716 | ||||
-rw-r--r-- | mdoc_hash.c | 94 | ||||
-rw-r--r-- | mdoc_html.c | 2284 | ||||
-rw-r--r-- | mdoc_macro.c | 1787 | ||||
-rw-r--r-- | mdoc_man.c | 637 | ||||
-rw-r--r-- | mdoc_term.c | 2257 | ||||
-rw-r--r-- | mdoc_validate.c | 2403 | ||||
-rw-r--r-- | msec.c | 37 | ||||
-rw-r--r-- | msec.in | 40 | ||||
-rw-r--r-- | out.c | 303 | ||||
-rw-r--r-- | out.h | 71 | ||||
-rw-r--r-- | preconv.1 | 158 | ||||
-rw-r--r-- | preconv.c | 528 | ||||
-rw-r--r-- | predefs.in | 65 | ||||
-rw-r--r-- | read.c | 846 | ||||
-rw-r--r-- | roff.7 | 989 | ||||
-rw-r--r-- | roff.c | 1768 | ||||
-rw-r--r-- | st.c | 39 | ||||
-rw-r--r-- | st.in | 78 | ||||
-rw-r--r-- | style.css | 144 | ||||
-rw-r--r-- | tbl.7 | 348 | ||||
-rw-r--r-- | tbl.c | 175 | ||||
-rw-r--r-- | tbl_data.c | 276 | ||||
-rw-r--r-- | tbl_html.c | 151 | ||||
-rw-r--r-- | tbl_layout.c | 472 | ||||
-rw-r--r-- | tbl_opts.c | 270 | ||||
-rw-r--r-- | tbl_term.c | 444 | ||||
-rw-r--r-- | term.c | 736 | ||||
-rw-r--r-- | term.h | 128 | ||||
-rw-r--r-- | term_ascii.c | 289 | ||||
-rw-r--r-- | term_ps.c | 1185 | ||||
-rw-r--r-- | test-fgetln.c | 11 | ||||
-rw-r--r-- | test-getsubopt.c | 12 | ||||
-rw-r--r-- | test-mmap.c | 10 | ||||
-rw-r--r-- | test-strlcat.c | 8 | ||||
-rw-r--r-- | test-strlcpy.c | 8 | ||||
-rw-r--r-- | test-strptime.c | 13 | ||||
-rw-r--r-- | tree.c | 349 | ||||
-rw-r--r-- | vol.c | 39 | ||||
-rw-r--r-- | vol.in | 35 | ||||
-rw-r--r-- | whatis.1 | 190 |
105 files changed, 45115 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 000000000000..304237b47827 --- /dev/null +++ b/Makefile @@ -0,0 +1,645 @@ +.PHONY: clean install installwww +.SUFFIXES: .sgml .html .md5 .h .h.html +.SUFFIXES: .1 .3 .7 .8 +.SUFFIXES: .1.txt .3.txt .7.txt .8.txt +.SUFFIXES: .1.pdf .3.pdf .7.pdf .8.pdf +.SUFFIXES: .1.ps .3.ps .7.ps .8.ps +.SUFFIXES: .1.html .3.html .7.html .8.html +.SUFFIXES: .1.xhtml .3.xhtml .7.xhtml .8.xhtml + +# Specify this if you want to hard-code the operating system to appear +# in the lower-left hand corner of -mdoc manuals. +# +# CFLAGS += -DOSNAME="\"OpenBSD 4.5\"" + +VERSION = 1.12.1 +VDATE = 23 March 2012 + +# IFF your system supports multi-byte functions (setlocale(), wcwidth(), +# putwchar()) AND has __STDC_ISO_10646__ (that is, wchar_t is simply a +# UCS-4 value) should you define USE_WCHAR. If you define it and your +# system DOESN'T support this, -Tlocale will produce garbage. +# If you don't define it, -Tlocale is a synonym for -Tacsii. +# +CFLAGS += -DUSE_WCHAR + +# If your system has manpath(1), uncomment this. This is most any +# system that's not OpenBSD or NetBSD. If uncommented, apropos(1), +# mandocdb(8), and man.cgi will popen(3) manpath(1) to get the MANPATH +# variable. +#CFLAGS += -DUSE_MANPATH + +# If your system supports static binaries only, uncomment this. This +# appears only to be BSD UNIX systems (Mac OS X has no support and Linux +# requires -pthreads for static libdb). +STATIC = -static + +CFLAGS += -g -DHAVE_CONFIG_H -DVERSION="\"$(VERSION)\"" +CFLAGS += -W -Wall -Wstrict-prototypes -Wno-unused-parameter -Wwrite-strings +PREFIX = /usr/local +WWWPREFIX = /var/www +HTDOCDIR = $(WWWPREFIX)/htdocs +CGIBINDIR = $(WWWPREFIX)/cgi-bin +BINDIR = $(PREFIX)/bin +INCLUDEDIR = $(PREFIX)/include/mandoc +LIBDIR = $(PREFIX)/lib/mandoc +MANDIR = $(PREFIX)/man +EXAMPLEDIR = $(PREFIX)/share/examples/mandoc +INSTALL = install +INSTALL_PROGRAM = $(INSTALL) -m 0755 +INSTALL_DATA = $(INSTALL) -m 0444 +INSTALL_LIB = $(INSTALL) -m 0644 +INSTALL_SOURCE = $(INSTALL) -m 0644 +INSTALL_MAN = $(INSTALL_DATA) + +# Non-BSD systems (Linux, etc.) need -ldb to compile mandocdb and +# apropos. +# However, if you don't have -ldb at all (or it's not native), then +# comment out apropos and mandocdb. +# +#DBLIB = -ldb +DBBIN = apropos mandocdb man.cgi catman whatis +DBLN = llib-lapropos.ln llib-lmandocdb.ln llib-lman.cgi.ln llib-lcatman.ln + +all: mandoc preconv demandoc $(DBBIN) + +SRCS = Makefile \ + TODO \ + apropos.1 \ + apropos.c \ + apropos_db.c \ + apropos_db.h \ + arch.c \ + arch.in \ + att.c \ + att.in \ + catman.8 \ + catman.c \ + cgi.c \ + chars.c \ + chars.in \ + compat_fgetln.c \ + compat_getsubopt.c \ + compat_strlcat.c \ + compat_strlcpy.c \ + config.h.post \ + config.h.pre \ + demandoc.1 \ + demandoc.c \ + eqn.7 \ + eqn.c \ + eqn_html.c \ + eqn_term.c \ + example.style.css \ + external.png \ + html.c \ + html.h \ + index.css \ + index.sgml \ + lib.c \ + lib.in \ + libman.h \ + libmandoc.h \ + libmdoc.h \ + libroff.h \ + main.c \ + main.h \ + man.7 \ + man.c \ + man.cgi.7 \ + man-cgi.css \ + man.h \ + man_hash.c \ + man_html.c \ + man_macro.c \ + man_term.c \ + man_validate.c \ + mandoc.1 \ + mandoc.3 \ + mandoc.c \ + mandoc.h \ + mandoc_char.7 \ + mandocdb.8 \ + mandocdb.c \ + mandocdb.h \ + manpath.c \ + manpath.h \ + mdoc.7 \ + mdoc.c \ + mdoc.h \ + mdoc_argv.c \ + mdoc_hash.c \ + mdoc_html.c \ + mdoc_macro.c \ + mdoc_man.c \ + mdoc_term.c \ + mdoc_validate.c \ + msec.c \ + msec.in \ + out.c \ + out.h \ + preconv.1 \ + preconv.c \ + predefs.in \ + read.c \ + roff.7 \ + roff.c \ + st.c \ + st.in \ + style.css \ + tbl.7 \ + tbl.c \ + tbl_data.c \ + tbl_html.c \ + tbl_layout.c \ + tbl_opts.c \ + tbl_term.c \ + term.c \ + term.h \ + term_ascii.c \ + term_ps.c \ + test-fgetln.c \ + test-getsubopt.c \ + test-mmap.c \ + test-strlcat.c \ + test-strlcpy.c \ + test-strptime.c \ + tree.c \ + vol.c \ + vol.in \ + whatis.1 + +LIBMAN_OBJS = man.o \ + man_hash.o \ + man_macro.o \ + man_validate.o +LIBMAN_LNS = man.ln \ + man_hash.ln \ + man_macro.ln \ + man_validate.ln + +LIBMDOC_OBJS = arch.o \ + att.o \ + lib.o \ + mdoc.o \ + mdoc_argv.o \ + mdoc_hash.o \ + mdoc_macro.o \ + mdoc_validate.o \ + st.o \ + vol.o +LIBMDOC_LNS = arch.ln \ + att.ln \ + lib.ln \ + mdoc.ln \ + mdoc_argv.ln \ + mdoc_hash.ln \ + mdoc_macro.ln \ + mdoc_validate.ln \ + st.ln \ + vol.ln + +LIBROFF_OBJS = eqn.o \ + roff.o \ + tbl.o \ + tbl_data.o \ + tbl_layout.o \ + tbl_opts.o +LIBROFF_LNS = eqn.ln \ + roff.ln \ + tbl.ln \ + tbl_data.ln \ + tbl_layout.ln \ + tbl_opts.ln + +LIBMANDOC_OBJS = $(LIBMAN_OBJS) \ + $(LIBMDOC_OBJS) \ + $(LIBROFF_OBJS) \ + chars.o \ + mandoc.o \ + msec.o \ + read.o +LIBMANDOC_LNS = $(LIBMAN_LNS) \ + $(LIBMDOC_LNS) \ + $(LIBROFF_LNS) \ + chars.ln \ + mandoc.ln \ + msec.ln \ + read.ln + +COMPAT_OBJS = compat_fgetln.o \ + compat_getsubopt.o \ + compat_strlcat.o \ + compat_strlcpy.o +COMPAT_LNS = compat_fgetln.ln \ + compat_getsubopt.ln \ + compat_strlcat.ln \ + compat_strlcpy.ln + +arch.o arch.ln: arch.in +att.o att.ln: att.in +chars.o chars.ln: chars.in +lib.o lib.ln: lib.in +msec.o msec.ln: msec.in +roff.o roff.ln: predefs.in +st.o st.ln: st.in +vol.o vol.ln: vol.in + +$(LIBMAN_OBJS) $(LIBMAN_LNS): libman.h +$(LIBMDOC_OBJS) $(LIBMDOC_LNS): libmdoc.h +$(LIBROFF_OBJS) $(LIBROFF_LNS): libroff.h +$(LIBMANDOC_OBJS) $(LIBMANDOC_LNS): mandoc.h mdoc.h man.h libmandoc.h config.h + +$(COMPAT_OBJS) $(COMPAT_LNS): config.h + +MANDOC_HTML_OBJS = eqn_html.o \ + html.o \ + man_html.o \ + mdoc_html.o \ + tbl_html.o +MANDOC_HTML_LNS = eqn_html.ln \ + html.ln \ + man_html.ln \ + mdoc_html.ln \ + tbl_html.ln + +MANDOC_MAN_OBJS = mdoc_man.o +MANDOC_MAN_LNS = mdoc_man.ln + +MANDOC_TERM_OBJS = eqn_term.o \ + man_term.o \ + mdoc_term.o \ + term.o \ + term_ascii.o \ + term_ps.o \ + tbl_term.o +MANDOC_TERM_LNS = eqn_term.ln \ + man_term.ln \ + mdoc_term.ln \ + term.ln \ + term_ascii.ln \ + term_ps.ln \ + tbl_term.ln + +MANDOC_OBJS = $(MANDOC_HTML_OBJS) \ + $(MANDOC_MAN_OBJS) \ + $(MANDOC_TERM_OBJS) \ + main.o \ + out.o \ + tree.o +MANDOC_LNS = $(MANDOC_HTML_LNS) \ + $(MANDOC_MAN_LNS) \ + $(MANDOC_TERM_LNS) \ + main.ln \ + out.ln \ + tree.ln + +$(MANDOC_HTML_OBJS) $(MANDOC_HTML_LNS): html.h +$(MANDOC_TERM_OBJS) $(MANDOC_TERM_LNS): term.h +$(MANDOC_OBJS) $(MANDOC_LNS): main.h mandoc.h mdoc.h man.h config.h out.h + +MANDOCDB_OBJS = mandocdb.o manpath.o +MANDOCDB_LNS = mandocdb.ln manpath.ln + +$(MANDOCDB_OBJS) $(MANDOCDB_LNS): mandocdb.h mandoc.h mdoc.h man.h config.h manpath.h + +PRECONV_OBJS = preconv.o +PRECONV_LNS = preconv.ln + +$(PRECONV_OBJS) $(PRECONV_LNS): config.h + +APROPOS_OBJS = apropos.o apropos_db.o manpath.o +APROPOS_LNS = apropos.ln apropos_db.ln manpath.ln + +$(APROPOS_OBJS) $(APROPOS_LNS): config.h mandoc.h apropos_db.h manpath.h mandocdb.h + +CGI_OBJS = $(MANDOC_HTML_OBJS) \ + $(MANDOC_MAN_OBJS) \ + $(MANDOC_TERM_OBJS) \ + cgi.o \ + apropos_db.o \ + manpath.o \ + out.o \ + tree.o + +CGI_LNS = $(MANDOC_HTML_LNS) \ + $(MANDOC_MAN_LNS) \ + $(MANDOC_TERM_LNS) \ + cgi.ln \ + apropos_db.ln \ + manpath.ln \ + out.ln \ + tree.ln + +$(CGI_OBJS) $(CGI_LNS): main.h mdoc.h man.h out.h config.h mandoc.h apropos_db.h manpath.h mandocdb.h + +CATMAN_OBJS = catman.o manpath.o +CATMAN_LNS = catman.ln manpath.ln + +$(CATMAN_OBJS) $(CATMAN_LNS): config.h mandoc.h manpath.h mandocdb.h + +DEMANDOC_OBJS = demandoc.o +DEMANDOC_LNS = demandoc.ln + +$(DEMANDOC_OBJS) $(DEMANDOC_LNS): config.h + +INDEX_MANS = apropos.1.html \ + apropos.1.xhtml \ + apropos.1.ps \ + apropos.1.pdf \ + apropos.1.txt \ + catman.8.html \ + catman.8.xhtml \ + catman.8.ps \ + catman.8.pdf \ + catman.8.txt \ + demandoc.1.html \ + demandoc.1.xhtml \ + demandoc.1.ps \ + demandoc.1.pdf \ + demandoc.1.txt \ + mandoc.1.html \ + mandoc.1.xhtml \ + mandoc.1.ps \ + mandoc.1.pdf \ + mandoc.1.txt \ + whatis.1.html \ + whatis.1.xhtml \ + whatis.1.ps \ + whatis.1.pdf \ + whatis.1.txt \ + mandoc.3.html \ + mandoc.3.xhtml \ + mandoc.3.ps \ + mandoc.3.pdf \ + mandoc.3.txt \ + eqn.7.html \ + eqn.7.xhtml \ + eqn.7.ps \ + eqn.7.pdf \ + eqn.7.txt \ + man.7.html \ + man.7.xhtml \ + man.7.ps \ + man.7.pdf \ + man.7.txt \ + man.cgi.7.html \ + man.cgi.7.xhtml \ + man.cgi.7.ps \ + man.cgi.7.pdf \ + man.cgi.7.txt \ + mandoc_char.7.html \ + mandoc_char.7.xhtml \ + mandoc_char.7.ps \ + mandoc_char.7.pdf \ + mandoc_char.7.txt \ + mdoc.7.html \ + mdoc.7.xhtml \ + mdoc.7.ps \ + mdoc.7.pdf \ + mdoc.7.txt \ + preconv.1.html \ + preconv.1.xhtml \ + preconv.1.ps \ + preconv.1.pdf \ + preconv.1.txt \ + roff.7.html \ + roff.7.xhtml \ + roff.7.ps \ + roff.7.pdf \ + roff.7.txt \ + tbl.7.html \ + tbl.7.xhtml \ + tbl.7.ps \ + tbl.7.pdf \ + tbl.7.txt \ + mandocdb.8.html \ + mandocdb.8.xhtml \ + mandocdb.8.ps \ + mandocdb.8.pdf \ + mandocdb.8.txt + +$(INDEX_MANS): mandoc + +INDEX_OBJS = $(INDEX_MANS) \ + man.h.html \ + mandoc.h.html \ + mdoc.h.html \ + mdocml.tar.gz \ + mdocml.md5 + +www: index.html + +lint: llib-lmandoc.ln llib-lpreconv.ln llib-ldemandoc.ln $(DBLN) + +clean: + rm -f libmandoc.a $(LIBMANDOC_OBJS) + rm -f llib-llibmandoc.ln $(LIBMANDOC_LNS) + rm -f mandocdb $(MANDOCDB_OBJS) + rm -f llib-lmandocdb.ln $(MANDOCDB_LNS) + rm -f preconv $(PRECONV_OBJS) + rm -f llib-lpreconv.ln $(PRECONV_LNS) + rm -f apropos whatis $(APROPOS_OBJS) + rm -f llib-lapropos.ln $(APROPOS_LNS) + rm -f man.cgi $(CGI_OBJS) + rm -f llib-lman.cgi.ln $(CGI_LNS) + rm -f catman $(CATMAN_OBJS) + rm -f llib-lcatman.ln $(CATMAN_LNS) + rm -f demandoc $(DEMANDOC_OBJS) + rm -f llib-ldemandoc.ln $(DEMANDOC_LNS) + rm -f mandoc $(MANDOC_OBJS) + rm -f llib-lmandoc.ln $(MANDOC_LNS) + rm -f config.h config.log $(COMPAT_OBJS) $(COMPAT_LNS) + rm -f mdocml.tar.gz mdocml-win32.zip mdocml-win64.zip mdocml-macosx.zip + rm -f index.html $(INDEX_OBJS) + rm -rf test-fgetln.dSYM + rm -rf test-strlcpy.dSYM + rm -rf test-strlcat.dSYM + rm -rf test-strptime.dSYM + rm -rf test-mmap.dSYM + rm -rf test-getsubopt.dSYM + rm -rf apropos.dSYM + rm -rf catman.dSYM + rm -rf mandocdb.dSYM + rm -rf whatis.dSYM + +install: all + mkdir -p $(DESTDIR)$(BINDIR) + mkdir -p $(DESTDIR)$(EXAMPLEDIR) + mkdir -p $(DESTDIR)$(LIBDIR) + mkdir -p $(DESTDIR)$(INCLUDEDIR) + mkdir -p $(DESTDIR)$(MANDIR)/man1 + mkdir -p $(DESTDIR)$(MANDIR)/man3 + mkdir -p $(DESTDIR)$(MANDIR)/man7 + $(INSTALL_PROGRAM) mandoc preconv demandoc $(DESTDIR)$(BINDIR) + $(INSTALL_LIB) libmandoc.a $(DESTDIR)$(LIBDIR) + $(INSTALL_LIB) man.h mdoc.h mandoc.h $(DESTDIR)$(INCLUDEDIR) + $(INSTALL_MAN) mandoc.1 preconv.1 demandoc.1 $(DESTDIR)$(MANDIR)/man1 + $(INSTALL_MAN) mandoc.3 $(DESTDIR)$(MANDIR)/man3 + $(INSTALL_MAN) man.7 mdoc.7 roff.7 eqn.7 tbl.7 mandoc_char.7 $(DESTDIR)$(MANDIR)/man7 + $(INSTALL_DATA) example.style.css $(DESTDIR)$(EXAMPLEDIR) + +installcgi: all + mkdir -p $(DESTDIR)$(CGIBINDIR) + mkdir -p $(DESTDIR)$(HTDOCDIR) + $(INSTALL_PROGRAM) man.cgi $(DESTDIR)$(CGIBINDIR) + $(INSTALL_DATA) example.style.css $(DESTDIR)$(HTDOCDIR)/man.css + $(INSTALL_DATA) man-cgi.css $(DESTDIR)$(HTDOCDIR) + +installwww: www + mkdir -p $(PREFIX)/snapshots + mkdir -p $(PREFIX)/binaries + $(INSTALL_DATA) index.html external.png index.css $(PREFIX) + $(INSTALL_DATA) $(INDEX_MANS) style.css $(PREFIX) + $(INSTALL_DATA) mandoc.h.html man.h.html mdoc.h.html $(PREFIX) + $(INSTALL_DATA) mdocml.tar.gz $(PREFIX)/snapshots + $(INSTALL_DATA) mdocml.md5 $(PREFIX)/snapshots + $(INSTALL_DATA) mdocml.tar.gz $(PREFIX)/snapshots/mdocml-$(VERSION).tar.gz + $(INSTALL_DATA) mdocml.md5 $(PREFIX)/snapshots/mdocml-$(VERSION).md5 + +libmandoc.a: $(COMPAT_OBJS) $(LIBMANDOC_OBJS) + $(AR) rs $@ $(COMPAT_OBJS) $(LIBMANDOC_OBJS) + +llib-llibmandoc.ln: $(COMPAT_LNS) $(LIBMANDOC_LNS) + $(LINT) $(LINTFLAGS) -Clibmandoc $(COMPAT_LNS) $(LIBMANDOC_LNS) + +mandoc: $(MANDOC_OBJS) libmandoc.a + $(CC) $(LDFLAGS) -o $@ $(MANDOC_OBJS) libmandoc.a + +llib-lmandoc.ln: $(MANDOC_LNS) llib-llibmandoc.ln + $(LINT) $(LINTFLAGS) -Cmandoc $(MANDOC_LNS) llib-llibmandoc.ln + +mandocdb: $(MANDOCDB_OBJS) libmandoc.a + $(CC) $(LDFLAGS) -o $@ $(MANDOCDB_OBJS) libmandoc.a $(DBLIB) + +llib-lmandocdb.ln: $(MANDOCDB_LNS) llib-llibmandoc.ln + $(LINT) $(LINTFLAGS) -Cmandocdb $(MANDOCDB_LNS) llib-llibmandoc.ln + +preconv: $(PRECONV_OBJS) + $(CC) $(LDFLAGS) -o $@ $(PRECONV_OBJS) + +llib-lpreconv.ln: $(PRECONV_LNS) llib-llibmandoc.ln + $(LINT) $(LINTFLAGS) -Cpreconv $(PRECONV_LNS) llib-llibmandoc.ln + +whatis: apropos + cp -f apropos whatis + +apropos: $(APROPOS_OBJS) libmandoc.a + $(CC) $(LDFLAGS) -o $@ $(APROPOS_OBJS) libmandoc.a $(DBLIB) + +llib-lapropos.ln: $(APROPOS_LNS) llib-llibmandoc.ln + $(LINT) $(LINTFLAGS) -Capropos $(APROPOS_LNS) llib-llibmandoc.ln + +catman: $(CATMAN_OBJS) libmandoc.a + $(CC) $(LDFLAGS) -o $@ $(CATMAN_OBJS) libmandoc.a $(DBLIB) + +llib-lcatman.ln: $(CATMAN_LNS) llib-llibmandoc.ln + $(LINT) $(LINTFLAGS) -Ccatman $(CATMAN_LNS) llib-llibmandoc.ln + +man.cgi: $(CGI_OBJS) libmandoc.a + $(CC) $(LDFLAGS) $(STATIC) -o $@ $(CGI_OBJS) libmandoc.a $(DBLIB) + +llib-lman.cgi.ln: $(CGI_LNS) llib-llibmandoc.ln + $(LINT) $(LINTFLAGS) -Cman.cgi $(CGI_LNS) llib-llibmandoc.ln + +demandoc: $(DEMANDOC_OBJS) libmandoc.a + $(CC) $(LDFLAGS) -o $@ $(DEMANDOC_OBJS) libmandoc.a + +llib-ldemandoc.ln: $(DEMANDOC_LNS) llib-llibmandoc.ln + $(LINT) $(LINTFLAGS) -Cdemandoc $(DEMANDOC_LNS) llib-llibmandoc.ln + +mdocml.md5: mdocml.tar.gz + md5 mdocml.tar.gz >$@ + +mdocml.tar.gz: $(SRCS) + mkdir -p .dist/mdocml-$(VERSION)/ + $(INSTALL_SOURCE) $(SRCS) .dist/mdocml-$(VERSION) + ( cd .dist/ && tar zcf ../$@ ./ ) + rm -rf .dist/ + +mdocml-win32.zip: $(SRCS) + mkdir -p .win32/mdocml-$(VERSION)/ + $(INSTALL_SOURCE) $(SRCS) .win32 + cp .win32/Makefile .win32/Makefile.old + egrep -v -e DUSE_WCHAR -e ^DBBIN .win32/Makefile.old >.win32/Makefile + ( cd .win32; \ + CC=i686-w64-mingw32-gcc AR=i686-w64-mingw32-ar CFLAGS='-DOSNAME=\"Windows\"' make; \ + make install PREFIX=mdocml-$(VERSION) ; \ + zip -r ../$@ mdocml-$(VERSION) ) + rm -rf .win32 + +mdocml-win64.zip: $(SRCS) + mkdir -p .win64/mdocml-$(VERSION)/ + $(INSTALL_SOURCE) $(SRCS) .win64 + cp .win64/Makefile .win64/Makefile.old + egrep -v -e DUSE_WCHAR -e ^DBBIN .win64/Makefile.old >.win64/Makefile + ( cd .win64; \ + CC=x86_64-w64-mingw32-gcc AR=x86_64-w64-mingw32-ar CFLAGS='-DOSNAME=\"Windows\"' make; \ + make install PREFIX=mdocml-$(VERSION) ; \ + zip -r ../$@ mdocml-$(VERSION) ) + rm -rf .win64 + +mdocml-macosx.zip: $(SRCS) + mkdir -p .macosx/mdocml-$(VERSION)/ + $(INSTALL_SOURCE) $(SRCS) .macosx + ( cd .macosx; \ + CFLAGS="-arch i386 -arch x86_64 -arch ppc" LDFLAGS="-arch i386 -arch x86_64 -arch ppc" make; \ + make install PREFIX=mdocml-$(VERSION) ; \ + zip -r ../$@ mdocml-$(VERSION) ) + rm -rf .macosx + +index.html: $(INDEX_OBJS) + +config.h: config.h.pre config.h.post + rm -f config.log + ( cat config.h.pre; \ + echo; \ + if $(CC) $(CFLAGS) -Werror -o test-fgetln test-fgetln.c >> config.log 2>&1; then \ + echo '#define HAVE_FGETLN'; \ + rm test-fgetln; \ + fi; \ + if $(CC) $(CFLAGS) -Werror -o test-strptime test-strptime.c >> config.log 2>&1; then \ + echo '#define HAVE_STRPTIME'; \ + rm test-strptime; \ + fi; \ + if $(CC) $(CFLAGS) -Werror -o test-getsubopt test-getsubopt.c >> config.log 2>&1; then \ + echo '#define HAVE_GETSUBOPT'; \ + rm test-getsubopt; \ + fi; \ + if $(CC) $(CFLAGS) -Werror -o test-strlcat test-strlcat.c >> config.log 2>&1; then \ + echo '#define HAVE_STRLCAT'; \ + rm test-strlcat; \ + fi; \ + if $(CC) $(CFLAGS) -Werror -o test-mmap test-mmap.c >> config.log 2>&1; then \ + echo '#define HAVE_MMAP'; \ + rm test-mmap; \ + fi; \ + if $(CC) $(CFLAGS) -Werror -o test-strlcpy test-strlcpy.c >> config.log 2>&1; then \ + echo '#define HAVE_STRLCPY'; \ + rm test-strlcpy; \ + fi; \ + echo; \ + cat config.h.post \ + ) > $@ + +.h.h.html: + highlight -I $< >$@ + +.1.1.txt .3.3.txt .7.7.txt .8.8.txt: + ./mandoc -Tascii -Wall,stop $< | col -b >$@ + +.1.1.html .3.3.html .7.7.html .8.8.html: + ./mandoc -Thtml -Wall,stop -Ostyle=style.css,man=%N.%S.html,includes=%I.html $< >$@ + +.1.1.ps .3.3.ps .7.7.ps .8.8.ps: + ./mandoc -Tps -Wall,stop $< >$@ + +.1.1.xhtml .3.3.xhtml .7.7.xhtml .8.8.xhtml: + ./mandoc -Txhtml -Wall,stop -Ostyle=style.css,man=%N.%S.xhtml,includes=%I.html $< >$@ + +.1.1.pdf .3.3.pdf .7.7.pdf .8.8.pdf: + ./mandoc -Tpdf -Wall,stop $< >$@ + +.sgml.html: + validate --warn $< + sed -e "s!@VERSION@!$(VERSION)!" -e "s!@VDATE@!$(VDATE)!" $< >$@ @@ -0,0 +1,372 @@ +************************************************************************ +* Official mandoc TODO. +* $Id: TODO,v 1.129 2012/03/04 23:53:37 schwarze Exp $ +************************************************************************ + +************************************************************************ +* parser bugs +************************************************************************ + +- ".\}" on its own line gets translated to bare ".\&" + which forces pset() into man(7) + and then triggers an unknown macro error + reported by naddy@ Sun, 3 Jul 2011 21:52:24 +0200 + +************************************************************************ +* formatter bugs +************************************************************************ + +- tbl(7): Horizontal and vertical lines are formatted badly: + With the box option, there is too much white space at the end of cells. + Horizontal lines from "=" lines are a bit too long. + yuri dot pankov at gmail dot com Thu, 14 Apr 2011 05:45:26 +0400 + +************************************************************************ +* missing features +************************************************************************ + +--- missing roff features ---------------------------------------------- + +- The pod2man preamble wants \h'...' with quoted numerical arguments, + see for example AUTHORS in MooseX::Getopt.3p, p5-MooseX-Getopt. + reported by Andreas Voegele <mail at andreasvoegele dot com> + Tue, 22 Nov 2011 15:34:47 +0100 on ports@ + +- .if n \{ + .br\} + should cause an extra space to be raised. + +- .ad (adjust margins) + .ad l -- adjust left margin only (flush left) + .ad r -- adjust right margin only (flush right) + .ad c -- center text on line + .ad b -- adjust both margins (alias: .ad n) + .na -- temporarily disable adjustment without changing the mode + .ad -- re-enable adjustment without changing the mode + Adjustment mode is ignored while in no-fill mode (.nf). + +- .it (line traps) occur in mysql(1), yasm_arch(7) + generated by DocBook XSL Stylesheets v1.71.1 <http://docbook.sf.net/> + reported by brad@ Sat, 15 Jan 2011 15:48:18 -0500 + +- .ns (no-space mode) occurs in xine-config(1) + reported by brad@ Sat, 15 Jan 2011 15:45:23 -0500 + +- xloadimage(1) wants .ti (temporary indent), rep by naddy@ + +- .ta (tab settings) occurs in ircbug(1) and probably gnats(1) + reported by brad@ Sat, 15 Jan 2011 15:50:51 -0500 + +- \c (interrupted text) occurs in chat(8) + +- using undefined strings or macros defines them to be empty + wl@ Mon, 14 Nov 2011 14:37:01 +0000 + +--- missing mdoc features ---------------------------------------------- + +- fix bad block nesting involving multiple identical explicit blocks + see the OpenBSD mdoc_macro.c 1.47 commit message + +- .Bl -column .Xo support is missing + ultimate goal: + restore .Xr and .Dv to + lib/libc/compat-43/sigvec.3 + lib/libc/gen/signal.3 + lib/libc/sys/sigaction.2 + +- edge case: decide how to deal with blk_full bad nesting, e.g. + .Sh .Nm .Bk .Nm .Ek .Sh found by jmc@ in ssh-keygen(1) + from jmc@ Wed, 14 Jul 2010 18:10:32 +0100 + +- \\ is now implemented correctly + * when defining strings and macros using .ds and .de + * when parsing roff(7) and man(7) macro arguments + It does not yet work in mdoc(7) macro arguments + because libmdoc does not yet use mandoc_getarg(). + Also check what happens in plain text, it must be identical to \e. + +- .Bd -filled should not be the same as .Bd -ragged, but align both + the left and right margin. In groff, it is implemented in terms + of .ad b, which we don't have either. Found in cksum(1). + +- implement blank `Bl -column', such as + .Bl -column + .It foo Ta bar + .El + +- explicitly disallow nested `Bl -column', which would clobber internal + flags defined for struct mdoc_macro + +- In .Bl -column .It, the end of the line probably has to be regarded + as an implicit .Ta, if there could be one, see the following mildly + ugly code from login.conf(5): + .Bl -column minpasswordlen program xetcxmotd + .It path Ta path Ta value of Dv _PATH_DEFPATH + .br + Default search path. + reported by Michal Mazurek <akfaew at jasminek dot net> + via jmc@ Thu, 7 Apr 2011 16:00:53 +0059 + +- inside `.Bl -column' phrases, punctuation is handled like normal + text, e.g. `.Bl -column .It Fl x . Ta ...' should give "-x -." + +- inside `.Bl -column' phrases, TERMP_IGNDELIM handling by `Pf' + is not safe, e.g. `.Bl -column .It Pf a b .' gives "ab." + but should give "ab ." + +- set a meaningful default if no `Bl' list type is assigned + +- have a blank `It' head for `Bl -tag' not puke + +- prohibit `Nm' from having non-text HEAD children + (e.g., NetBSD mDNSShared/dns-sd.1) + (mdoc_html.c and mdoc_term.c `Nm' handlers can be slightly simplified) + +- When there is free text in the SYNOPSIS and that free text contains + the .Nm macro, groff somehow understands to treat the .Nm as an in-line + macro, while mandoc treats it as a block macro and breaks the line. + No idea how the logic for distinguishing in-line and block instances + should be, needs investigation. + uqs@ Thu, 2 Jun 2011 11:03:51 +0200 + uqs@ Thu, 2 Jun 2011 11:33:35 +0200 + +--- missing man features ----------------------------------------------- + +- groff an-ext.tmac macros (.UR, .UE) occur in xine(5) + reported by brad@ Sat, 15 Jan 2011 15:45:23 -0500 + +- -T[x]html doesn't stipulate non-collapsing spaces in literal mode + +--- missing tbl features ----------------------------------------------- + +- implement basic non-parametric .de to support e.g. sox(1) + reported by naddy@ Sat, 16 Oct 2010 23:51:57 +0200 + *** sox(1) still doesn't work, tbl(1) errors need investigation + +- allow standalone `.' to be interpreted as an end-of-layout + delimiter instead of being thrown away as a no-op roff line + reported by Yuri Pankov, Wed 18 May 2011 11:34:59 CEST + +--- missing misc features ---------------------------------------------- + +- clean up escape sequence handling, creating three classes: + (1) fully implemented, or parsed and ignored without loss of content + (2) unimplemented, potentially causing loss of content + or serious mangling of formatting (e.g. \n) -> ERROR + see textproc/mgdiff(1) for nice examples + (3) undefined, just output the character -> perhaps WARNING + +- The \t escape sequence is the same as a literal tab, see for example + the ASCII table in hexdump(1) where + .Bl -column \&000_nu \&001_so \&002_st \&003_et \&004_eo + .It \&000\ nul\t001\ soh\t002\ stx\t003\ etx\t004\ eot\t005\ enq + produces + 000 nul 001 soh 002 stx 003 etx 004 eot 005 enq + and the example in oldrdist(1) + +- look at pages generated from reStructeredText, e.g. devel/mercurial hg(1) + These are a weird mixture of man(7) and custom autogenerated low-level + roff stuff. Figure out to what extent we can cope. + For details, see http://docutils.sourceforge.net/rst.html + noted by stsp@ Sat, 24 Apr 2010 09:17:55 +0200 + reminded by nicm@ Mon, 3 May 2010 09:52:41 +0100 + +- check compatibility with Plan9: + http://swtch.com/usr/local/plan9/tmac/tmac.an + http://swtch.com/plan9port/man/man7/man.html + "Anthony J. Bentley" <anthonyjbentley@gmail.com> 28 Dec 2010 21:58:40 -0700 + +************************************************************************ +* formatting issues: ugly output +************************************************************************ + +- a column list with blank `Ta' cells triggers a spurrious + start-with-whitespace printing of a newline + +- double quotes inside double quotes are escaped by doubling them + implement this in mdoc(7), too + so far, we only have it in roff(7) and man(7) + reminded by millert@ Thu, 09 Dec 2010 17:29:52 -0500 + +- perl(1) SYNOPSIS looks bad; reported by deraadt@ + 1) man(7) seems to need SYNOPSIS .Nm blocks, too + +- In .Bl -column, + .It Em Authentication<tab>Key Length + ought to render "Key Length" with emphasis, too, + see OpenBSD iked.conf(5). + reported again Nicolas Joly via wiz@ Wed, 12 Oct 2011 00:20:00 +0200 + +- empty phrases in .Bl column produce too few blanks + try e.g. .Bl -column It Ta Ta + reported by millert Fri, 02 Apr 2010 16:13:46 -0400 + +- .%T can have trailing punctuation. Currently, it puts the trailing + punctuation into a trailing MDOC_TEXT element inside its own scope. + That element should rather be outside its scope, such that the + punctuation does not get underlines. This is not trivial to + implement because .%T then needs some features of in_line_eoln() - + slurp all arguments into one single text element - and one feature + of in_line() - put trailing punctuation out of scope. + Found in mount_nfs(8) and exports(5), search for "Appendix". + +- in enclosures, mandoc sometimes fancies a bogus end of sentence + reminded by jmc@ Thu, 23 Sep 2010 18:13:39 +0059 + +************************************************************************ +* formatting issues: gratuitous differences +************************************************************************ + +- .Rv (and probably .Ex) print different text if an `Nm' has been named + or not (run a manual without `Nm blah' to see this). I'm not sure + that this exists in the wild, but it's still an error. + +- In .Bl -bullet, the groff bullet is "+\b+\bo\bo", the mandoc bullet + is just "o\bo". + see for example OpenBSD ksh(1) + +- The characters "|" and "\*(Ba" should never be bold, + not even in the middle of a word, e.g. ".Cm b\*(Bac" in + "mknod [-m mode] name b|c major minor" + in OpenBSD ksh(1) + +- A bogus .Pp between two .It must not produce a double blank line, + see between -R and -r in OpenBSD rm(1), before "update" in mount(8), + or in DIAGNOSTICS in init(8), or before "is always true" in ksh(1). + The same happens with .Pp just before .El, see bgpd.conf(5). + Also have `It' complain if `Pp' is invoked at certain times (not + -compact?). + +- .Pp between two .It in .Bl -column should produce one, + not two blank lines, see e.g. login.conf(5). + reported by jmc@ Sun, 17 Apr 2011 14:04:58 +0059 + reported again by sthen@ Wed, 18 Jan 2012 02:09:39 +0000 (UTC) + +- If the *first* line after .It is .Pp, break the line right after + the tag, do not pad with space characters before breaking. + See the description of the a, c, and i commands in sed(1). + +- If the first line after .It is .D1, do not assert a blank line + in between, see for example tmux(1). + reported by nicm@ 13 Jan 2011 00:18:57 +0000 + +- .Nx 1.0a + should be "NetBSD 1.0A", not "NetBSD 1.0a", + see OpenBSD ccdconfig(8). + +- In .Bl -tag, if a tag exceeds the right margin and must be continued + on the next line, it must be indented by -width, not width+1; + see "rule block|pass" in OpenBSD ifconfig(8). + +- When the -width string contains macros, the macros must be rendered + before measuring the width, for example + .Bl -tag -width ".Dv message" + in magic(5), located in src/usr.bin/file, is the same + as -width 7n, not -width 11n. + The same applies to .Bl -column column widths; + reported again by Nicolas Joly Thu, 1 Mar 2012 13:41:26 +0100 via wiz@ 5 Mar + +- The \& zero-width character counts as output. + That is, when it is alone on a line between two .Pp, + we want three blank lines, not two as in mandoc. + +- When .Fn arguments exceed one output line, all but the first + should be indented, see e.g. rpc(3); + reported by jmc@ on discuss@ Fri, 29 Oct 2010 13:48:33 +0100 + reported again by Nicolas Joly via wiz@ Sun, 18 Sep 2011 18:24:40 +0200 + Also, we don't want to break the line within the argument of: + .Fa "chtype tl" + +- .Ns should work when called at the end of an input line, see + the following code in vi(1): + .It Xo + .Op Ar line + .Cm a Ns Op Cm ppend Ns + .Op Cm !\& + .Xc + The input text is appended after the specified line. + +- Header lines of excessive length: + Port OpenBSD man_term.c rev. 1.25 to mdoc_term.c + and document it in mdoc(7) and man(7) COMPATIBILITY + found while talking to Chris Bennett + +- In man(7), the sequence + .HP + one line of regular text + .SH + should not produce two blank lines before the .SH, + see for example named-checkconf(8). + +- In man(7), the sequence + .SH HEADER + <blank line> + .PP + regular text + should not produce any blank lines between the header and the text, + see for example rsync(1). + Reported by naddy@ Mon, 28 Mar 2011 20:45:42 +0200 + +- In man(7), the sequence + regular text + .IP + .IP "tag" + indented text + should produce one, not four blank lines between the regular text + and the tag, see for example rsync(1). + Likewise, + regular text + .IP + indented text + should produce one, not two blank lines in between, and + regular text + .IP + .RS + .IP tag + indented text + should produce one, not three blank lines. + Reported by naddy@ Mon, 28 Mar 2011 20:45:42 +0200 + +- trailing whitespace must be ignored even when followed by a font escape, + see for example + makes + \fBdig \fR + operate in batch mode + in dig(1). + +************************************************************************ +* error reporting issues +************************************************************************ + +- .TP directly followed by .RS gives an assertion. + +************************************************************************ +* performance issues +************************************************************************ + +Several areas can be cleaned up to make mandoc even faster. These are + +- improve hashing mechanism for macros (quite important: performance) + +- improve hashing mechanism for characters (not as important) + +- the PDF file is HUGE: this can be reduced by using relative offsets + +- instead of re-initialising the roff predefined-strings set before each + parse, create a read-only version the first time and copy it + +************************************************************************ +* structural issues +************************************************************************ + +- We use the input line number at several places to distinguish + same-line from different-line input. That plainly doesn't work + with user-defined macros, leading to random breakage. + +- Find better ways to prevent endless loops + in roff(7) macro and string expansion. + +- Finish cleanup of date handling. + Decide which formats should be recognized where. + Update both mdoc(7) and man(7) documentation. + Triggered by Tim van der Molen Tue, 22 Feb 2011 20:30:45 +0100 diff --git a/apropos.1 b/apropos.1 new file mode 100644 index 000000000000..7dea132a461e --- /dev/null +++ b/apropos.1 @@ -0,0 +1,328 @@ +.\" $Id: apropos.1,v 1.17 2012/03/24 01:46:25 kristaps Exp $ +.\" +.\" Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: March 24 2012 $ +.Dt APROPOS 1 +.Os +.Sh NAME +.Nm apropos +.Nd search manual page databases +.Sh SYNOPSIS +.Nm +.Op Fl C Ar file +.Op Fl M Ar manpath +.Op Fl m Ar manpath +.Op Fl S Ar arch +.Op Fl s Ar section +.Ar expression ... +.Sh DESCRIPTION +The +.Nm +utility queries manual page databases generated by +.Xr mandocdb 8 , +evaluating on +.Ar expression +for each file in each database. +.Pp +By default, +.Nm +searches for +.Xr mandocdb 8 +databases in the default paths stipulated by +.Xr man 1 , +parses terms as case-sensitive regular expressions +over manual names and descriptions. +Multiple terms imply pairwise +.Fl o . +If standard output is a TTY, a result may be selected from a list and +its manual displayed with the pager. +.Pp +Its arguments are as follows: +.Bl -tag -width Ds +.It Fl C Ar file +Specify an alternative configuration +.Ar file +in +.Xr man.conf 5 +format. +.It Fl M Ar manpath +Use the colon-separated path instead of the default list of paths +searched for +.Xr mandocdb 8 +databases. +Invalid paths, or paths without manual databases, are ignored. +.It Fl m Ar manpath +Prepend the colon-separated paths to the list of paths searched +for +.Xr mandocdb 8 +databases. +Invalid paths, or paths without manual databases, are ignored. +.It Fl S Ar arch +Search only for a particular architecture. +.It Fl s Ar cat +Search only for a manual section. +See +.Xr man 1 +for a listing of manual sections. +.El +.Pp +An +.Ar expression +consists of search terms joined by logical operators +.Fl a +.Pq and +and +.Fl o +.Pq or . +The +.Fl a +operator has precedence over +.Fl o +and both are evaluated left-to-right. +.Bl -tag -width Ds +.It \&( Ar expr No \&) +True if the subexpression +.Ar expr +is true. +.It Ar expr1 Fl a Ar expr2 +True if both +.Ar expr1 +and +.Ar expr2 +are true (logical +.Qq and ) . +.It Ar expr1 Oo Fl o Oc Ar expr2 +True if +.Ar expr1 +and/or +.Ar expr2 +evaluate to true (logical +.Qq or ) . +.It Ar term +True if +.Ar term +is satisfied. +This has syntax +.Li [key[,key]*(=~)]?val , +where operand +.Cm key +is an +.Xr mdoc 7 +macro to query and +.Cm val +is its value. +See +.Sx Macro Keys +for a list of available keys. +Operator +.Li \&= +evaluates a substring, while +.Li \&~ +evaluates a regular expression. +.It Fl i Ar term +If +.Ar term +is a regular expression, it +is evaluated case-insensitively. +Has no effect on substring terms. +.El +.Pp +Results are sorted by manual title, with output formatted as +.Pp +.D1 title(sec) \- description +.Pp +Where +.Qq title +is the manual's title (note multiple manual names may exist for one +title), +.Qq sec +is the manual section, and +.Qq description +is the manual's short description. +If an architecture is specified for the manual, it is displayed as +.Pp +.D1 title(cat/arch) \- description +.Pp +If on a TTY, results are prefixed with a numeric identifier. +.Pp +.D1 [index] title(cat) \- description +.Pp +One may choose a manual be entering the index at the prompt. +Valid choices are displayed using +.Ev MANPAGER , +or failing that , +.Ev PAGER +or just +.Xr more 1 . +Source pages are formatted with +.Xr mandoc 1 ; +preformatted pages with +.Xr cat 1 . +.Ss Macro Keys +Queries evaluate over a subset of +.Xr mdoc 7 +macros indexed by +.Xr mandocdb 8 . +In addition to the macro keys listed below, the special key +.Cm any +may be used to match any available macro key. +.Pp +Names and description: +.Bl -column "xLix" description -offset indent -compact +.It Li \&Nm Ta manual name +.It Li \&Nd Ta one-line manual description +.El +.Pp +Sections and cross references: +.Bl -column "xLix" description -offset indent -compact +.It Li \&Sh Ta section header (excluding standard sections) +.It Li \&Ss Ta subsection header +.It Li \&Xr Ta cross reference to another manual page +.It Li \&Rs Ta bibliographic reference +.El +.Pp +Semantic markup for command line utilities: +.Bl -column "xLix" description -offset indent -compact +.It Li \&Fl Ta command line options (flags) +.It Li \&Cm Ta command modifier +.It Li \&Ar Ta command argument +.It Li \&Ic Ta internal or interactive command +.It Li \&Ev Ta environmental variable +.It Li \&Pa Ta file system path +.El +.Pp +Semantic markup for function libraries: +.Bl -column "xLix" description -offset indent -compact +.It Li \&Lb Ta function library name +.It Li \&In Ta include file +.It Li \&Ft Ta function return type +.It Li \&Fn Ta function name +.It Li \&Fa Ta function argument type and name +.It Li \&Vt Ta variable type +.It Li \&Va Ta variable name +.It Li \&Dv Ta defined variable or preprocessor constant +.It Li \&Er Ta error constant +.It Li \&Ev Ta environmental variable +.El +.Pp +Various semantic markup: +.Bl -column "xLix" description -offset indent -compact +.It Li \&An Ta author name +.It Li \&Lk Ta hyperlink +.It Li \&Mt Ta Do mailto Dc hyperlink +.It Li \&Cd Ta kernel configuration declaration +.It Li \&Ms Ta mathematical symbol +.It Li \&Tn Ta tradename +.El +.Pp +Physical markup: +.Bl -column "xLix" description -offset indent -compact +.It Li \&Em Ta italic font or underline +.It Li \&Sy Ta boldface font +.It Li \&Li Ta typewriter font +.El +.Pp +Text production: +.Bl -column "xLix" description -offset indent -compact +.It Li \&St Ta reference to a standards document +.It Li \&At Ta At No version reference +.It Li \&Bx Ta Bx No version reference +.It Li \&Bsx Ta Bsx No version reference +.It Li \&Nx Ta Nx No version reference +.It Li \&Fx Ta Fx No version reference +.It Li \&Ox Ta Ox No version reference +.It Li \&Dx Ta Dx No version reference +.El +.Sh ENVIRONMENT +.Bl -tag -width Ds +.It Ev MANPAGER +Default pager for manuals. +If this is unset, falls back to +.Ev Pager . +.It Ev PAGER +The second choice for a manual pager. +If this is unset, use +.Xr more 1 . +.It Ev MANPATH +Colon-separated paths modifying the default list of paths searched for +manual databases. +Invalid paths, or paths without manual databases, are ignored. +Overridden by +.Fl M . +If +.Ev MANPATH +begins with a +.Sq \&: , +it is appended to the default list; +else if it ends with +.Sq \&: , +it is prepended to the default list; else if it contains +.Sq \&:: , +the default list is inserted between the colons. +If none of these conditions are met, it overrides the default list. +.El +.Sh FILES +.Bl -tag -width "/etc/man.conf" -compact +.It Pa whatis.db +name of the +.Xr mandocdb 8 +keyword database +.It Pa whatis.index +name of the +.Xr mandocdb 8 +filename database +.It Pa /etc/man.conf +default +.Xr man 1 +configuration file +.El +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +Search for +.Qq mdoc +as a substring and regular expression +within each manual name and description: +.Pp +.Dl $ apropos mdoc +.Dl $ apropos ~^mdoc$ +.Pp +Include matches for +.Qq roff +and +.Qq man +for the regular expression case: +.Pp +.Dl $ apropos ~^mdoc$ roff man +.Dl $ apropos ~^mdoc$ \-o roff \-o man +.Pp +Search for +.Qq optind +and +.Qq optarg +as variable names in the library category: +.Pp +.Dl $ apropos \-s 3 Va~^optind \-a Va~^optarg$ +.Sh SEE ALSO +.Xr more 1 +.Xr re_format 7 , +.Xr mandocdb 8 +.Sh AUTHORS +The +.Nm +utility was written by +.An Kristaps Dzonsons , +.Mt kristaps@bsd.lv . diff --git a/apropos.c b/apropos.c new file mode 100644 index 000000000000..9c3cae96afa0 --- /dev/null +++ b/apropos.c @@ -0,0 +1,239 @@ +/* $Id: apropos.c,v 1.30 2012/03/24 02:18:51 kristaps Exp $ */ +/* + * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv> + * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include <sys/param.h> + +#include <assert.h> +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "apropos_db.h" +#include "mandoc.h" +#include "manpath.h" + +#define SINGLETON(_res, _sz) \ + ((_sz) && (_res)[0].matched && \ + (1 == (_sz) || 0 == (_res)[1].matched)) +#define EMPTYSET(_res, _sz) \ + ((0 == (_sz)) || 0 == (_res)[0].matched) + +static int cmp(const void *, const void *); +static void list(struct res *, size_t, void *); +static void usage(void); + +static char *progname; + +int +main(int argc, char *argv[]) +{ + int ch, rc, whatis, usecat; + struct res *res; + struct manpaths paths; + const char *prog; + pid_t pid; + char path[PATH_MAX]; + int fds[2]; + size_t terms, ressz, sz; + struct opts opts; + struct expr *e; + char *defpaths, *auxpaths, *conf_file, *cp; + extern int optind; + extern char *optarg; + + progname = strrchr(argv[0], '/'); + if (progname == NULL) + progname = argv[0]; + else + ++progname; + + whatis = 0 == strncmp(progname, "whatis", 6); + + memset(&paths, 0, sizeof(struct manpaths)); + memset(&opts, 0, sizeof(struct opts)); + + usecat = 0; + ressz = 0; + res = NULL; + auxpaths = defpaths = NULL; + conf_file = NULL; + e = NULL; + path[0] = '\0'; + + while (-1 != (ch = getopt(argc, argv, "C:M:m:S:s:"))) + switch (ch) { + case ('C'): + conf_file = optarg; + break; + case ('M'): + defpaths = optarg; + break; + case ('m'): + auxpaths = optarg; + break; + case ('S'): + opts.arch = optarg; + break; + case ('s'): + opts.cat = optarg; + break; + default: + usage(); + return(EXIT_FAILURE); + } + + argc -= optind; + argv += optind; + + if (0 == argc) + return(EXIT_SUCCESS); + + rc = 0; + + manpath_parse(&paths, conf_file, defpaths, auxpaths); + + e = whatis ? termcomp(argc, argv, &terms) : + exprcomp(argc, argv, &terms); + + if (NULL == e) { + fprintf(stderr, "%s: Bad expression\n", progname); + goto out; + } + + rc = apropos_search + (paths.sz, paths.paths, &opts, + e, terms, NULL, &ressz, &res, list); + + terms = 1; + + if (0 == rc) { + fprintf(stderr, "%s: Bad database\n", progname); + goto out; + } else if ( ! isatty(STDOUT_FILENO) || EMPTYSET(res, ressz)) + goto out; + + if ( ! SINGLETON(res, ressz)) { + printf("Which manpage would you like [1]? "); + fflush(stdout); + if (NULL != (cp = fgetln(stdin, &sz)) && + sz > 1 && '\n' == cp[--sz]) { + if ((ch = atoi(cp)) <= 0) + goto out; + terms = (size_t)ch; + } + } + + if (--terms < ressz && res[terms].matched) { + chdir(paths.paths[res[terms].volume]); + strlcpy(path, res[terms].file, PATH_MAX); + usecat = RESTYPE_CAT == res[terms].type; + } +out: + manpath_free(&paths); + resfree(res, ressz); + exprfree(e); + + if ('\0' == path[0]) + return(rc ? EXIT_SUCCESS : EXIT_FAILURE); + + if (-1 == pipe(fds)) { + perror(NULL); + exit(EXIT_FAILURE); + } + + if (-1 == (pid = fork())) { + perror(NULL); + exit(EXIT_FAILURE); + } else if (pid > 0) { + dup2(fds[0], STDIN_FILENO); + close(fds[1]); + prog = NULL != getenv("MANPAGER") ? + getenv("MANPAGER") : + (NULL != getenv("PAGER") ? + getenv("PAGER") : "more"); + execlp(prog, prog, (char *)NULL); + perror(prog); + return(EXIT_FAILURE); + } + + dup2(fds[1], STDOUT_FILENO); + close(fds[0]); + prog = usecat ? "cat" : "mandoc"; + execlp(prog, prog, path, (char *)NULL); + perror(prog); + return(EXIT_FAILURE); +} + +/* ARGSUSED */ +static void +list(struct res *res, size_t sz, void *arg) +{ + size_t i; + + qsort(res, sz, sizeof(struct res), cmp); + + if (EMPTYSET(res, sz) || SINGLETON(res, sz)) + return; + + if ( ! isatty(STDOUT_FILENO)) + for (i = 0; i < sz && res[i].matched; i++) + printf("%s(%s%s%s) - %.70s\n", + res[i].title, res[i].cat, + *res[i].arch ? "/" : "", + *res[i].arch ? res[i].arch : "", + res[i].desc); + else + for (i = 0; i < sz && res[i].matched; i++) + printf("[%zu] %s(%s%s%s) - %.70s\n", i + 1, + res[i].title, res[i].cat, + *res[i].arch ? "/" : "", + *res[i].arch ? res[i].arch : "", + res[i].desc); +} + +static int +cmp(const void *p1, const void *p2) +{ + const struct res *r1 = p1; + const struct res *r2 = p2; + + if (0 == r1->matched) + return(1); + else if (0 == r2->matched) + return(1); + + return(strcasecmp(r1->title, r2->title)); +} + +static void +usage(void) +{ + + fprintf(stderr, "usage: %s " + "[-C file] " + "[-M manpath] " + "[-m manpath] " + "[-S arch] " + "[-s section] " + "expression ...\n", + progname); +} diff --git a/apropos_db.c b/apropos_db.c new file mode 100644 index 000000000000..8aea771da791 --- /dev/null +++ b/apropos_db.c @@ -0,0 +1,876 @@ +/* $Id: apropos_db.c,v 1.31 2012/03/24 01:46:25 kristaps Exp $ */ +/* + * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv> + * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <assert.h> +#include <fcntl.h> +#include <regex.h> +#include <stdarg.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#if defined(__linux__) +# include <endian.h> +# include <db_185.h> +#elif defined(__APPLE__) +# include <libkern/OSByteOrder.h> +# include <db.h> +#else +# include <db.h> +#endif + +#include "mandocdb.h" +#include "apropos_db.h" +#include "mandoc.h" + +#define RESFREE(_x) \ + do { \ + free((_x)->file); \ + free((_x)->cat); \ + free((_x)->title); \ + free((_x)->arch); \ + free((_x)->desc); \ + free((_x)->matches); \ + } while (/*CONSTCOND*/0) + +struct expr { + int regex; /* is regex? */ + int index; /* index in match array */ + uint64_t mask; /* type-mask */ + int and; /* is rhs of logical AND? */ + char *v; /* search value */ + regex_t re; /* compiled re, if regex */ + struct expr *next; /* next in sequence */ + struct expr *subexpr; +}; + +struct type { + uint64_t mask; + const char *name; +}; + +struct rectree { + struct res *node; /* record array for dir tree */ + int len; /* length of record array */ +}; + +static const struct type types[] = { + { TYPE_An, "An" }, + { TYPE_Ar, "Ar" }, + { TYPE_At, "At" }, + { TYPE_Bsx, "Bsx" }, + { TYPE_Bx, "Bx" }, + { TYPE_Cd, "Cd" }, + { TYPE_Cm, "Cm" }, + { TYPE_Dv, "Dv" }, + { TYPE_Dx, "Dx" }, + { TYPE_Em, "Em" }, + { TYPE_Er, "Er" }, + { TYPE_Ev, "Ev" }, + { TYPE_Fa, "Fa" }, + { TYPE_Fl, "Fl" }, + { TYPE_Fn, "Fn" }, + { TYPE_Fn, "Fo" }, + { TYPE_Ft, "Ft" }, + { TYPE_Fx, "Fx" }, + { TYPE_Ic, "Ic" }, + { TYPE_In, "In" }, + { TYPE_Lb, "Lb" }, + { TYPE_Li, "Li" }, + { TYPE_Lk, "Lk" }, + { TYPE_Ms, "Ms" }, + { TYPE_Mt, "Mt" }, + { TYPE_Nd, "Nd" }, + { TYPE_Nm, "Nm" }, + { TYPE_Nx, "Nx" }, + { TYPE_Ox, "Ox" }, + { TYPE_Pa, "Pa" }, + { TYPE_Rs, "Rs" }, + { TYPE_Sh, "Sh" }, + { TYPE_Ss, "Ss" }, + { TYPE_St, "St" }, + { TYPE_Sy, "Sy" }, + { TYPE_Tn, "Tn" }, + { TYPE_Va, "Va" }, + { TYPE_Va, "Vt" }, + { TYPE_Xr, "Xr" }, + { UINT64_MAX, "any" }, + { 0, NULL } +}; + +static DB *btree_open(void); +static int btree_read(const DBT *, const DBT *, + const struct mchars *, + uint64_t *, recno_t *, char **); +static int expreval(const struct expr *, int *); +static void exprexec(const struct expr *, + const char *, uint64_t, struct res *); +static int exprmark(const struct expr *, + const char *, uint64_t, int *); +static struct expr *exprexpr(int, char *[], int *, int *, size_t *); +static struct expr *exprterm(char *, int); +static DB *index_open(void); +static int index_read(const DBT *, const DBT *, int, + const struct mchars *, struct res *); +static void norm_string(const char *, + const struct mchars *, char **); +static size_t norm_utf8(unsigned int, char[7]); +static int single_search(struct rectree *, const struct opts *, + const struct expr *, size_t terms, + struct mchars *, int); + +/* + * Open the keyword mandoc-db database. + */ +static DB * +btree_open(void) +{ + BTREEINFO info; + DB *db; + + memset(&info, 0, sizeof(BTREEINFO)); + info.lorder = 4321; + info.flags = R_DUP; + + db = dbopen(MANDOC_DB, O_RDONLY, 0, DB_BTREE, &info); + if (NULL != db) + return(db); + + return(NULL); +} + +/* + * Read a keyword from the database and normalise it. + * Return 0 if the database is insane, else 1. + */ +static int +btree_read(const DBT *k, const DBT *v, const struct mchars *mc, + uint64_t *mask, recno_t *rec, char **buf) +{ + uint64_t vbuf[2]; + + /* Are our sizes sane? */ + if (k->size < 2 || sizeof(vbuf) != v->size) + return(0); + + /* Is our string nil-terminated? */ + if ('\0' != ((const char *)k->data)[(int)k->size - 1]) + return(0); + + norm_string((const char *)k->data, mc, buf); + memcpy(vbuf, v->data, v->size); + *mask = betoh64(vbuf[0]); + *rec = betoh64(vbuf[1]); + return(1); +} + +/* + * Take a Unicode codepoint and produce its UTF-8 encoding. + * This isn't the best way to do this, but it works. + * The magic numbers are from the UTF-8 packaging. + * They're not as scary as they seem: read the UTF-8 spec for details. + */ +static size_t +norm_utf8(unsigned int cp, char out[7]) +{ + int rc; + + rc = 0; + + if (cp <= 0x0000007F) { + rc = 1; + out[0] = (char)cp; + } else if (cp <= 0x000007FF) { + rc = 2; + out[0] = (cp >> 6 & 31) | 192; + out[1] = (cp & 63) | 128; + } else if (cp <= 0x0000FFFF) { + rc = 3; + out[0] = (cp >> 12 & 15) | 224; + out[1] = (cp >> 6 & 63) | 128; + out[2] = (cp & 63) | 128; + } else if (cp <= 0x001FFFFF) { + rc = 4; + out[0] = (cp >> 18 & 7) | 240; + out[1] = (cp >> 12 & 63) | 128; + out[2] = (cp >> 6 & 63) | 128; + out[3] = (cp & 63) | 128; + } else if (cp <= 0x03FFFFFF) { + rc = 5; + out[0] = (cp >> 24 & 3) | 248; + out[1] = (cp >> 18 & 63) | 128; + out[2] = (cp >> 12 & 63) | 128; + out[3] = (cp >> 6 & 63) | 128; + out[4] = (cp & 63) | 128; + } else if (cp <= 0x7FFFFFFF) { + rc = 6; + out[0] = (cp >> 30 & 1) | 252; + out[1] = (cp >> 24 & 63) | 128; + out[2] = (cp >> 18 & 63) | 128; + out[3] = (cp >> 12 & 63) | 128; + out[4] = (cp >> 6 & 63) | 128; + out[5] = (cp & 63) | 128; + } else + return(0); + + out[rc] = '\0'; + return((size_t)rc); +} + +/* + * Normalise strings from the index and database. + * These strings are escaped as defined by mandoc_char(7) along with + * other goop in mandoc.h (e.g., soft hyphens). + * This function normalises these into a nice UTF-8 string. + * Returns 0 if the database is fucked. + */ +static void +norm_string(const char *val, const struct mchars *mc, char **buf) +{ + size_t sz, bsz; + char utfbuf[7]; + const char *seq, *cpp; + int len, u, pos; + enum mandoc_esc esc; + static const char res[] = { '\\', '\t', + ASCII_NBRSP, ASCII_HYPH, '\0' }; + + /* Pre-allocate by the length of the input */ + + bsz = strlen(val) + 1; + *buf = mandoc_realloc(*buf, bsz); + pos = 0; + + while ('\0' != *val) { + /* + * Halt on the first escape sequence. + * This also halts on the end of string, in which case + * we just copy, fallthrough, and exit the loop. + */ + if ((sz = strcspn(val, res)) > 0) { + memcpy(&(*buf)[pos], val, sz); + pos += (int)sz; + val += (int)sz; + } + + if (ASCII_HYPH == *val) { + (*buf)[pos++] = '-'; + val++; + continue; + } else if ('\t' == *val || ASCII_NBRSP == *val) { + (*buf)[pos++] = ' '; + val++; + continue; + } else if ('\\' != *val) + break; + + /* Read past the slash. */ + + val++; + u = 0; + + /* + * Parse the escape sequence and see if it's a + * predefined character or special character. + */ + + esc = mandoc_escape(&val, &seq, &len); + if (ESCAPE_ERROR == esc) + break; + + /* + * XXX - this just does UTF-8, but we need to know + * beforehand whether we should do text substitution. + */ + + switch (esc) { + case (ESCAPE_SPECIAL): + if (0 != (u = mchars_spec2cp(mc, seq, len))) + break; + /* FALLTHROUGH */ + default: + continue; + } + + /* + * If we have a Unicode codepoint, try to convert that + * to a UTF-8 byte string. + */ + + cpp = utfbuf; + if (0 == (sz = norm_utf8(u, utfbuf))) + continue; + + /* Copy the rendered glyph into the stream. */ + + sz = strlen(cpp); + bsz += sz; + + *buf = mandoc_realloc(*buf, bsz); + + memcpy(&(*buf)[pos], cpp, sz); + pos += (int)sz; + } + + (*buf)[pos] = '\0'; +} + +/* + * Open the filename-index mandoc-db database. + * Returns NULL if opening failed. + */ +static DB * +index_open(void) +{ + DB *db; + + db = dbopen(MANDOC_IDX, O_RDONLY, 0, DB_RECNO, NULL); + if (NULL != db) + return(db); + + return(NULL); +} + +/* + * Safely unpack from an index file record into the structure. + * Returns 1 if an entry was unpacked, 0 if the database is insane. + */ +static int +index_read(const DBT *key, const DBT *val, int index, + const struct mchars *mc, struct res *rec) +{ + size_t left; + char *np, *cp; + char type; + +#define INDEX_BREAD(_dst) \ + do { \ + if (NULL == (np = memchr(cp, '\0', left))) \ + return(0); \ + norm_string(cp, mc, &(_dst)); \ + left -= (np - cp) + 1; \ + cp = np + 1; \ + } while (/* CONSTCOND */ 0) + + if (0 == (left = val->size)) + return(0); + + cp = val->data; + assert(sizeof(recno_t) == key->size); + memcpy(&rec->rec, key->data, key->size); + rec->volume = index; + + if ('d' == (type = *cp++)) + rec->type = RESTYPE_MDOC; + else if ('a' == type) + rec->type = RESTYPE_MAN; + else if ('c' == type) + rec->type = RESTYPE_CAT; + else + return(0); + + left--; + INDEX_BREAD(rec->file); + INDEX_BREAD(rec->cat); + INDEX_BREAD(rec->title); + INDEX_BREAD(rec->arch); + INDEX_BREAD(rec->desc); + return(1); +} + +/* + * Search mandocdb databases in paths for expression "expr". + * Filter out by "opts". + * Call "res" with the results, which may be zero. + * Return 0 if there was a database error, else return 1. + */ +int +apropos_search(int pathsz, char **paths, const struct opts *opts, + const struct expr *expr, size_t terms, void *arg, + size_t *sz, struct res **resp, + void (*res)(struct res *, size_t, void *)) +{ + struct rectree tree; + struct mchars *mc; + int i, rc; + + memset(&tree, 0, sizeof(struct rectree)); + + rc = 0; + mc = mchars_alloc(); + *sz = 0; + *resp = NULL; + + /* + * Main loop. Change into the directory containing manpage + * databases. Run our expession over each database in the set. + */ + + for (i = 0; i < pathsz; i++) { + if (chdir(paths[i])) + continue; + if (single_search(&tree, opts, expr, terms, mc, i)) + continue; + + resfree(tree.node, tree.len); + mchars_free(mc); + return(0); + } + + (*res)(tree.node, tree.len, arg); + *sz = tree.len; + *resp = tree.node; + mchars_free(mc); + return(1); +} + +static int +single_search(struct rectree *tree, const struct opts *opts, + const struct expr *expr, size_t terms, + struct mchars *mc, int vol) +{ + int root, leaf, ch; + DBT key, val; + DB *btree, *idx; + char *buf; + struct res *rs; + struct res r; + uint64_t mask; + recno_t rec; + + root = -1; + leaf = -1; + btree = NULL; + idx = NULL; + buf = NULL; + rs = tree->node; + + memset(&r, 0, sizeof(struct res)); + + if (NULL == (btree = btree_open())) + return(1); + + if (NULL == (idx = index_open())) { + (*btree->close)(btree); + return(1); + } + + while (0 == (ch = (*btree->seq)(btree, &key, &val, R_NEXT))) { + if ( ! btree_read(&key, &val, mc, &mask, &rec, &buf)) + break; + + /* + * See if this keyword record matches any of the + * expressions we have stored. + */ + if ( ! exprmark(expr, buf, mask, NULL)) + continue; + + /* + * O(log n) scan for prior records. Since a record + * number is unbounded, this has decent performance over + * a complex hash function. + */ + + for (leaf = root; leaf >= 0; ) + if (rec > rs[leaf].rec && + rs[leaf].rhs >= 0) + leaf = rs[leaf].rhs; + else if (rec < rs[leaf].rec && + rs[leaf].lhs >= 0) + leaf = rs[leaf].lhs; + else + break; + + /* + * If we find a record, see if it has already evaluated + * to true. If it has, great, just keep going. If not, + * try to evaluate it now and continue anyway. + */ + + if (leaf >= 0 && rs[leaf].rec == rec) { + if (0 == rs[leaf].matched) + exprexec(expr, buf, mask, &rs[leaf]); + continue; + } + + /* + * We have a new file to examine. + * Extract the manpage's metadata from the index + * database, then begin partial evaluation. + */ + + key.data = &rec; + key.size = sizeof(recno_t); + + if (0 != (*idx->get)(idx, &key, &val, 0)) + break; + + r.lhs = r.rhs = -1; + if ( ! index_read(&key, &val, vol, mc, &r)) + break; + + /* XXX: this should be elsewhere, I guess? */ + + if (opts->cat && strcasecmp(opts->cat, r.cat)) + continue; + + if (opts->arch && *r.arch) + if (strcasecmp(opts->arch, r.arch)) + continue; + + tree->node = rs = mandoc_realloc + (rs, (tree->len + 1) * sizeof(struct res)); + + memcpy(&rs[tree->len], &r, sizeof(struct res)); + memset(&r, 0, sizeof(struct res)); + rs[tree->len].matches = + mandoc_calloc(terms, sizeof(int)); + + exprexec(expr, buf, mask, &rs[tree->len]); + + /* Append to our tree. */ + + if (leaf >= 0) { + if (rec > rs[leaf].rec) + rs[leaf].rhs = tree->len; + else + rs[leaf].lhs = tree->len; + } else + root = tree->len; + + tree->len++; + } + + (*btree->close)(btree); + (*idx->close)(idx); + + free(buf); + RESFREE(&r); + return(1 == ch); +} + +void +resfree(struct res *rec, size_t sz) +{ + size_t i; + + for (i = 0; i < sz; i++) + RESFREE(&rec[i]); + free(rec); +} + +/* + * Compile a list of straight-up terms. + * The arguments are re-written into ~[[:<:]]term[[:>:]], or "term" + * surrounded by word boundaries, then pumped through exprterm(). + * Terms are case-insensitive. + * This emulates whatis(1) behaviour. + */ +struct expr * +termcomp(int argc, char *argv[], size_t *tt) +{ + char *buf; + int pos; + struct expr *e, *next; + size_t sz; + + buf = NULL; + e = NULL; + *tt = 0; + + for (pos = argc - 1; pos >= 0; pos--) { + sz = strlen(argv[pos]) + 18; + buf = mandoc_realloc(buf, sz); + strlcpy(buf, "Nm~[[:<:]]", sz); + strlcat(buf, argv[pos], sz); + strlcat(buf, "[[:>:]]", sz); + if (NULL == (next = exprterm(buf, 0))) { + free(buf); + exprfree(e); + return(NULL); + } + next->next = e; + e = next; + (*tt)++; + } + + free(buf); + return(e); +} + +/* + * Compile a sequence of logical expressions. + * See apropos.1 for a grammar of this sequence. + */ +struct expr * +exprcomp(int argc, char *argv[], size_t *tt) +{ + int pos, lvl; + struct expr *e; + + pos = lvl = 0; + *tt = 0; + + e = exprexpr(argc, argv, &pos, &lvl, tt); + + if (0 == lvl && pos >= argc) + return(e); + + exprfree(e); + return(NULL); +} + +/* + * Compile an array of tokens into an expression. + * An informal expression grammar is defined in apropos(1). + * Return NULL if we fail doing so. All memory will be cleaned up. + * Return the root of the expression sequence if alright. + */ +static struct expr * +exprexpr(int argc, char *argv[], int *pos, int *lvl, size_t *tt) +{ + struct expr *e, *first, *next; + int log; + + first = next = NULL; + + for ( ; *pos < argc; (*pos)++) { + e = next; + + /* + * Close out a subexpression. + */ + + if (NULL != e && 0 == strcmp(")", argv[*pos])) { + if (--(*lvl) < 0) + goto err; + break; + } + + /* + * Small note: if we're just starting, don't let "-a" + * and "-o" be considered logical operators: they're + * just tokens unless pairwise joining, in which case we + * record their existence (or assume "OR"). + */ + log = 0; + + if (NULL != e && 0 == strcmp("-a", argv[*pos])) + log = 1; + else if (NULL != e && 0 == strcmp("-o", argv[*pos])) + log = 2; + + if (log > 0 && ++(*pos) >= argc) + goto err; + + /* + * Now we parse the term part. This can begin with + * "-i", in which case the expression is case + * insensitive. + */ + + if (0 == strcmp("(", argv[*pos])) { + ++(*pos); + ++(*lvl); + next = mandoc_calloc(1, sizeof(struct expr)); + next->subexpr = exprexpr(argc, argv, pos, lvl, tt); + if (NULL == next->subexpr) { + free(next); + next = NULL; + } + } else if (0 == strcmp("-i", argv[*pos])) { + if (++(*pos) >= argc) + goto err; + next = exprterm(argv[*pos], 0); + } else + next = exprterm(argv[*pos], 1); + + if (NULL == next) + goto err; + + next->and = log == 1; + next->index = (int)(*tt)++; + + /* Append to our chain of expressions. */ + + if (NULL == first) { + assert(NULL == e); + first = next; + } else { + assert(NULL != e); + e->next = next; + } + } + + return(first); +err: + exprfree(first); + return(NULL); +} + +/* + * Parse a terminal expression with the grammar as defined in + * apropos(1). + * Return NULL if we fail the parse. + */ +static struct expr * +exprterm(char *buf, int cs) +{ + struct expr e; + struct expr *p; + char *key; + int i; + + memset(&e, 0, sizeof(struct expr)); + + /* Choose regex or substring match. */ + + if (NULL == (e.v = strpbrk(buf, "=~"))) { + e.regex = 0; + e.v = buf; + } else { + e.regex = '~' == *e.v; + *e.v++ = '\0'; + } + + /* Determine the record types to search for. */ + + e.mask = 0; + if (buf < e.v) { + while (NULL != (key = strsep(&buf, ","))) { + i = 0; + while (types[i].mask && + strcmp(types[i].name, key)) + i++; + e.mask |= types[i].mask; + } + } + if (0 == e.mask) + e.mask = TYPE_Nm | TYPE_Nd; + + if (e.regex) { + i = REG_EXTENDED | REG_NOSUB | (cs ? 0 : REG_ICASE); + if (regcomp(&e.re, e.v, i)) + return(NULL); + } + + e.v = mandoc_strdup(e.v); + + p = mandoc_calloc(1, sizeof(struct expr)); + memcpy(p, &e, sizeof(struct expr)); + return(p); +} + +void +exprfree(struct expr *p) +{ + struct expr *pp; + + while (NULL != p) { + if (p->subexpr) + exprfree(p->subexpr); + if (p->regex) + regfree(&p->re); + free(p->v); + pp = p->next; + free(p); + p = pp; + } +} + +static int +exprmark(const struct expr *p, const char *cp, + uint64_t mask, int *ms) +{ + + for ( ; p; p = p->next) { + if (p->subexpr) { + if (exprmark(p->subexpr, cp, mask, ms)) + return(1); + continue; + } else if ( ! (mask & p->mask)) + continue; + + if (p->regex) { + if (regexec(&p->re, cp, 0, NULL, 0)) + continue; + } else if (NULL == strcasestr(cp, p->v)) + continue; + + if (NULL == ms) + return(1); + else + ms[p->index] = 1; + } + + return(0); +} + +static int +expreval(const struct expr *p, int *ms) +{ + int match; + + /* + * AND has precedence over OR. Analysis is left-right, though + * it doesn't matter because there are no side-effects. + * Thus, step through pairwise ANDs and accumulate their Boolean + * evaluation. If we encounter a single true AND collection or + * standalone term, the whole expression is true (by definition + * of OR). + */ + + for (match = 0; p && ! match; p = p->next) { + /* Evaluate a subexpression, if applicable. */ + if (p->subexpr && ! ms[p->index]) + ms[p->index] = expreval(p->subexpr, ms); + + match = ms[p->index]; + for ( ; p->next && p->next->and; p = p->next) { + /* Evaluate a subexpression, if applicable. */ + if (p->next->subexpr && ! ms[p->next->index]) + ms[p->next->index] = + expreval(p->next->subexpr, ms); + match = match && ms[p->next->index]; + } + } + + return(match); +} + +/* + * First, update the array of terms for which this expression evaluates + * to true. + * Second, logically evaluate all terms over the updated array of truth + * values. + * If this evaluates to true, mark the expression as satisfied. + */ +static void +exprexec(const struct expr *e, const char *cp, + uint64_t mask, struct res *r) +{ + + assert(0 == r->matched); + exprmark(e, cp, mask, r->matches); + r->matched = expreval(e, r->matches); +} diff --git a/apropos_db.h b/apropos_db.h new file mode 100644 index 000000000000..72d4c204a391 --- /dev/null +++ b/apropos_db.h @@ -0,0 +1,73 @@ +/* $Id: apropos_db.h,v 1.13 2012/03/24 01:46:25 kristaps Exp $ */ +/* + * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef APROPOS_H +#define APROPOS_H + +enum restype { + RESTYPE_MAN, /* man(7) file */ + RESTYPE_MDOC, /* mdoc(7) file */ + RESTYPE_CAT /* pre-formatted file */ +}; + +struct res { + enum restype type; /* input file type */ + char *file; /* file in file-system */ + char *cat; /* category (3p, 3, etc.) */ + char *title; /* title (FOO, etc.) */ + char *arch; /* arch (or empty string) */ + char *desc; /* description (from Nd) */ + unsigned int rec; /* record in index */ + /* + * The index volume. This indexes into the array of directories + * searched for manual page databases. + */ + unsigned int volume; + /* + * The following fields are used internally. + * + * Maintain a binary tree for checking the uniqueness of `rec' + * when adding elements to the results array. + * Since the results array is dynamic, use offset in the array + * instead of a pointer to the structure. + */ + int lhs; + int rhs; + int matched; /* expression is true */ + int *matches; /* partial truth evaluations */ +}; + +struct opts { + const char *arch; /* restrict to architecture */ + const char *cat; /* restrict to manual section */ +}; + +__BEGIN_DECLS + +struct expr; + +int apropos_search(int, char **, const struct opts *, + const struct expr *, size_t, + void *, size_t *, struct res **, + void (*)(struct res *, size_t, void *)); +struct expr *exprcomp(int, char *[], size_t *); +void exprfree(struct expr *); +void resfree(struct res *, size_t); +struct expr *termcomp(int, char *[], size_t *); + +__END_DECLS + +#endif /*!APROPOS_H*/ diff --git a/arch.c b/arch.c new file mode 100644 index 000000000000..e764bfe9931f --- /dev/null +++ b/arch.c @@ -0,0 +1,39 @@ +/* $Id: arch.c,v 1.9 2011/03/22 14:33:05 kristaps Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "mdoc.h" +#include "mandoc.h" +#include "libmdoc.h" + +#define LINE(x, y) \ + if (0 == strcmp(p, x)) return(y); + +const char * +mdoc_a2arch(const char *p) +{ + +#include "arch.in" + + return(NULL); +} diff --git a/arch.in b/arch.in new file mode 100644 index 000000000000..5113446e468a --- /dev/null +++ b/arch.in @@ -0,0 +1,111 @@ +/* $Id: arch.in,v 1.12 2012/01/28 14:02:17 joerg Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This file defines the architecture token of the .Dt prologue macro. + * All architectures that your system supports (or the manuals of your + * system) should be included here. The right-hand-side is the + * formatted output. + * + * Be sure to escape strings. + * + * REMEMBER TO ADD NEW ARCHITECTURES TO MDOC.7! + */ + +LINE("acorn26", "Acorn26") +LINE("acorn32", "Acorn32") +LINE("algor", "Algor") +LINE("alpha", "Alpha") +LINE("amd64", "AMD64") +LINE("amiga", "Amiga") +LINE("amigappc", "AmigaPPC") +LINE("arc", "ARC") +LINE("arm", "ARM") +LINE("arm26", "ARM26") +LINE("arm32", "ARM32") +LINE("armish", "ARMISH") +LINE("aviion", "AViiON") +LINE("atari", "ATARI") +LINE("beagle", "Beagle") +LINE("bebox", "BeBox") +LINE("cats", "cats") +LINE("cesfic", "CESFIC") +LINE("cobalt", "Cobalt") +LINE("dreamcast", "Dreamcast") +LINE("emips", "EMIPS") +LINE("evbarm", "evbARM") +LINE("evbmips", "evbMIPS") +LINE("evbppc", "evbPPC") +LINE("evbsh3", "evbSH3") +LINE("ews4800mips", "EWS4800MIPS") +LINE("hp300", "HP300") +LINE("hp700", "HP700") +LINE("hpcarm", "HPCARM") +LINE("hpcmips", "HPCMIPS") +LINE("hpcsh", "HPCSH") +LINE("hppa", "HPPA") +LINE("hppa64", "HPPA64") +LINE("ia64", "ia64") +LINE("i386", "i386") +LINE("ibmnws", "IBMNWS") +LINE("iyonix", "Iyonix") +LINE("landisk", "LANDISK") +LINE("loongson", "Loongson") +LINE("luna68k", "Luna68k") +LINE("luna88k", "Luna88k") +LINE("m68k", "m68k") +LINE("mac68k", "Mac68k") +LINE("macppc", "MacPPC") +LINE("mips", "MIPS") +LINE("mips64", "MIPS64") +LINE("mipsco", "MIPSCo") +LINE("mmeye", "mmEye") +LINE("mvme68k", "MVME68k") +LINE("mvme88k", "MVME88k") +LINE("mvmeppc", "MVMEPPC") +LINE("netwinder", "NetWinder") +LINE("news68k", "NeWS68k") +LINE("newsmips", "NeWSMIPS") +LINE("next68k", "NeXT68k") +LINE("ofppc", "OFPPC") +LINE("palm", "Palm") +LINE("pc532", "PC532") +LINE("playstation2", "PlayStation2") +LINE("pmax", "PMAX") +LINE("pmppc", "pmPPC") +LINE("powerpc", "PowerPC") +LINE("prep", "PReP") +LINE("rs6000", "RS6000") +LINE("sandpoint", "Sandpoint") +LINE("sbmips", "SBMIPS") +LINE("sgi", "SGI") +LINE("sgimips", "SGIMIPS") +LINE("sh3", "SH3") +LINE("shark", "Shark") +LINE("socppc", "SOCPPC") +LINE("solbourne", "Solbourne") +LINE("sparc", "SPARC") +LINE("sparc64", "SPARC64") +LINE("sun2", "Sun2") +LINE("sun3", "Sun3") +LINE("tahoe", "Tahoe") +LINE("vax", "VAX") +LINE("x68k", "X68k") +LINE("x86", "x86") +LINE("x86_64", "x86_64") +LINE("xen", "Xen") +LINE("zaurus", "Zaurus") diff --git a/att.c b/att.c new file mode 100644 index 000000000000..24d757ddf75b --- /dev/null +++ b/att.c @@ -0,0 +1,39 @@ +/* $Id: att.c,v 1.9 2011/03/22 14:33:05 kristaps Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "mdoc.h" +#include "mandoc.h" +#include "libmdoc.h" + +#define LINE(x, y) \ + if (0 == strcmp(p, x)) return(y); + +const char * +mdoc_a2att(const char *p) +{ + +#include "att.in" + + return(NULL); +} diff --git a/att.in b/att.in new file mode 100644 index 000000000000..b4ef822158f8 --- /dev/null +++ b/att.in @@ -0,0 +1,40 @@ +/* $Id: att.in,v 1.8 2011/07/31 17:30:33 schwarze Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This file defines the AT&T versions of the .At macro. This probably + * isn't going to change. The right-hand side is the formatted string. + * + * Be sure to escape strings. + * The non-breaking blanks prevent ending an output line right before + * a number. Groff prevent line breaks at the same places. + */ + +LINE("v1", "Version\\~1 AT&T UNIX") +LINE("v2", "Version\\~2 AT&T UNIX") +LINE("v3", "Version\\~3 AT&T UNIX") +LINE("v4", "Version\\~4 AT&T UNIX") +LINE("v5", "Version\\~5 AT&T UNIX") +LINE("v6", "Version\\~6 AT&T UNIX") +LINE("v7", "Version\\~7 AT&T UNIX") +LINE("32v", "Version\\~32V AT&T UNIX") +LINE("III", "AT&T System\\~III UNIX") +LINE("V", "AT&T System\\~V UNIX") +LINE("V.1", "AT&T System\\~V Release\\~1 UNIX") +LINE("V.2", "AT&T System\\~V Release\\~2 UNIX") +LINE("V.3", "AT&T System\\~V Release\\~3 UNIX") +LINE("V.4", "AT&T System\\~V Release\\~4 UNIX") diff --git a/catman.8 b/catman.8 new file mode 100644 index 000000000000..f5246f9a6ced --- /dev/null +++ b/catman.8 @@ -0,0 +1,111 @@ +.\" $Id: catman.8,v 1.5 2011/12/25 19:35:44 kristaps Exp $ +.\" +.\" Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: December 25 2011 $ +.Dt CATMAN 8 +.Os +.Sh NAME +.Nm catman +.Nd update a man.cgi manpage cache +.Sh SYNOPSIS +.Nm catman +.Op Fl fv +.Op Fl C Ar file +.Op Fl M Ar manpath +.Op Fl m Ar manpath +.Op Fl o Ar path +.Sh DESCRIPTION +The +.Nm +utility updates cached manpages for a jailed +.Xr man.cgi 7 . +.Pp +By default, +.Nm +searches for +.Xr mandocdb 8 +databases in the default paths stipulated by +.Xr man 1 +and updates the cache in +.Pa /var/www/cache/man.cgi . +.Pp +Its arguments are as follows: +.Bl -tag -width Ds +.It Fl f +Force an update to all files. +.It Fl v +Print each file being updated. +.It Fl C Ar file +Specify an alternative configuration +.Ar file +in +.Xr man.conf 5 +format. +.It Fl M Ar manpath +Use the colon-separated path instead of the default list of paths +searched for +.Xr mandocdb 8 +databases. +Invalid paths, or paths without manual databases, are ignored. +.It Fl m Ar manpath +Prepend the colon-separated paths to the list of paths searched +for +.Xr mandocdb 8 +databases. +Invalid paths, or paths without manual databases, are ignored. +.It Fl o Ar path +Update into the directory tree under +.Ar path . +.El +.Pp +Cache updates occur when a +.Xr mandocdb 8 +database is older than the cached copy unless +.Fl f +is specified, in which case files are always considered out of date. +Cached manual pages are only updated if older than the master copy. +.Sh ENVIRONMENT +.Bl -tag -width Ds +.It Ev MANPATH +Colon-separated paths modifying the default list of paths searched for +manual databases. +Invalid paths, or paths without manual databases, are ignored. +Overridden by +.Fl M . +If +.Ev MANPATH +begins with a +.Sq \&: , +it is appended to the default list; +else if it ends with +.Sq \&: , +it is prepended to the default list; else if it contains +.Sq \&:: , +the default list is inserted between the colons. +If none of these conditions are met, it overrides the default list. +.El +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr mandoc 1 , +.Xr man.cgi 7 , +.Xr mandocdb 8 +.Sh AUTHORS +The +.Nm +utility was written by +.An Kristaps Dzonsons , +.Mt kristaps@bsd.lv . diff --git a/catman.c b/catman.c new file mode 100644 index 000000000000..1d313ea6e00e --- /dev/null +++ b/catman.c @@ -0,0 +1,511 @@ +/* $Id: catman.c,v 1.10 2012/01/03 15:17:20 kristaps Exp $ */ +/* + * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/wait.h> + +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#ifdef __linux__ +# include <db_185.h> +#else +# include <db.h> +#endif + +#include "manpath.h" +#include "mandocdb.h" + +#define xstrlcpy(_dst, _src, _sz) \ + do if (strlcpy((_dst), (_src), (_sz)) >= (_sz)) { \ + fprintf(stderr, "%s: Path too long", (_dst)); \ + exit(EXIT_FAILURE); \ + } while (/* CONSTCOND */0) + +#define xstrlcat(_dst, _src, _sz) \ + do if (strlcat((_dst), (_src), (_sz)) >= (_sz)) { \ + fprintf(stderr, "%s: Path too long", (_dst)); \ + exit(EXIT_FAILURE); \ + } while (/* CONSTCOND */0) + +static int indexhtml(char *, size_t, char *, size_t); +static int manup(const struct manpaths *, char *); +static int mkpath(char *, mode_t, mode_t); +static int treecpy(char *, char *); +static int update(char *, char *); +static void usage(void); + +static const char *progname; +static int verbose; +static int force; + +int +main(int argc, char *argv[]) +{ + int ch; + char *aux, *base, *conf_file; + struct manpaths dirs; + char buf[MAXPATHLEN]; + extern char *optarg; + extern int optind; + + progname = strrchr(argv[0], '/'); + if (progname == NULL) + progname = argv[0]; + else + ++progname; + + aux = base = conf_file = NULL; + xstrlcpy(buf, "/var/www/cache/man.cgi", MAXPATHLEN); + + while (-1 != (ch = getopt(argc, argv, "C:fm:M:o:v"))) + switch (ch) { + case ('C'): + conf_file = optarg; + break; + case ('f'): + force = 1; + break; + case ('m'): + aux = optarg; + break; + case ('M'): + base = optarg; + break; + case ('o'): + xstrlcpy(buf, optarg, MAXPATHLEN); + break; + case ('v'): + verbose++; + break; + default: + usage(); + return(EXIT_FAILURE); + } + + argc -= optind; + argv += optind; + + if (argc > 0) { + usage(); + return(EXIT_FAILURE); + } + + memset(&dirs, 0, sizeof(struct manpaths)); + manpath_parse(&dirs, conf_file, base, aux); + ch = manup(&dirs, buf); + manpath_free(&dirs); + return(ch ? EXIT_SUCCESS : EXIT_FAILURE); +} + +static void +usage(void) +{ + + fprintf(stderr, "usage: %s " + "[-fv] " + "[-C file] " + "[-o path] " + "[-m manpath] " + "[-M manpath]\n", + progname); +} + +/* + * If "src" file doesn't exist (errors out), return -1. Otherwise, + * return 1 if "src" is newer (which also happens "dst" doesn't exist) + * and 0 otherwise. + */ +static int +isnewer(const char *dst, const char *src) +{ + struct stat s1, s2; + + if (-1 == stat(src, &s1)) + return(-1); + if (force) + return(1); + + return(-1 == stat(dst, &s2) ? 1 : s1.st_mtime > s2.st_mtime); +} + +/* + * Copy the contents of one file into another. + * Returns 0 on failure, 1 on success. + */ +static int +filecpy(const char *dst, const char *src) +{ + char buf[BUFSIZ]; + int sfd, dfd, rc; + ssize_t rsz, wsz; + + sfd = dfd = -1; + rc = 0; + + if (-1 == (dfd = open(dst, O_CREAT|O_TRUNC|O_WRONLY, 0644))) { + perror(dst); + goto out; + } else if (-1 == (sfd = open(src, O_RDONLY, 0))) { + perror(src); + goto out; + } + + while ((rsz = read(sfd, buf, BUFSIZ)) > 0) + if (-1 == (wsz = write(dfd, buf, (size_t)rsz))) { + perror(dst); + goto out; + } else if (wsz < rsz) { + fprintf(stderr, "%s: Short write\n", dst); + goto out; + } + + if (rsz < 0) + perror(src); + else + rc = 1; +out: + if (-1 != sfd) + close(sfd); + if (-1 != dfd) + close(dfd); + + return(rc); +} + +/* + * Pass over the recno database and re-create HTML pages if they're + * found to be out of date. + * Returns -1 on fatal error, 1 on success. + */ +static int +indexhtml(char *src, size_t ssz, char *dst, size_t dsz) +{ + DB *idx; + DBT key, val; + int c, rc; + unsigned int fl; + const char *f; + char *d; + char fname[MAXPATHLEN]; + pid_t pid; + + pid = -1; + + xstrlcpy(fname, dst, MAXPATHLEN); + xstrlcat(fname, "/", MAXPATHLEN); + xstrlcat(fname, MANDOC_IDX, MAXPATHLEN); + + idx = dbopen(fname, O_RDONLY, 0, DB_RECNO, NULL); + if (NULL == idx) { + perror(fname); + return(-1); + } + + fl = R_FIRST; + while (0 == (c = (*idx->seq)(idx, &key, &val, fl))) { + fl = R_NEXT; + /* + * If the record is zero-length, then it's unassigned. + * Skip past these. + */ + if (0 == val.size) + continue; + + f = (const char *)val.data + 1; + if (NULL == memchr(f, '\0', val.size - 1)) + break; + + src[(int)ssz] = dst[(int)dsz] = '\0'; + + xstrlcat(dst, "/", MAXPATHLEN); + xstrlcat(dst, f, MAXPATHLEN); + + xstrlcat(src, "/", MAXPATHLEN); + xstrlcat(src, f, MAXPATHLEN); + + if (-1 == (rc = isnewer(dst, src))) { + fprintf(stderr, "%s: File missing\n", f); + break; + } else if (0 == rc) + continue; + + d = strrchr(dst, '/'); + assert(NULL != d); + *d = '\0'; + + if (-1 == mkpath(dst, 0755, 0755)) { + perror(dst); + break; + } + + *d = '/'; + + if ( ! filecpy(dst, src)) + break; + if (verbose) + printf("%s\n", dst); + } + + (*idx->close)(idx); + + if (c < 0) + perror(fname); + else if (0 == c) + fprintf(stderr, "%s: Corrupt index\n", fname); + + return(1 == c ? 1 : -1); +} + +/* + * Copy both recno and btree databases into the destination. + * Call in to begin recreating HTML files. + * Return -1 on fatal error and 1 if the update went well. + */ +static int +update(char *dst, char *src) +{ + size_t dsz, ssz; + + dsz = strlen(dst); + ssz = strlen(src); + + xstrlcat(src, "/", MAXPATHLEN); + xstrlcat(dst, "/", MAXPATHLEN); + + xstrlcat(src, MANDOC_DB, MAXPATHLEN); + xstrlcat(dst, MANDOC_DB, MAXPATHLEN); + + if ( ! filecpy(dst, src)) + return(-1); + if (verbose) + printf("%s\n", dst); + + dst[(int)dsz] = src[(int)ssz] = '\0'; + + xstrlcat(src, "/", MAXPATHLEN); + xstrlcat(dst, "/", MAXPATHLEN); + + xstrlcat(src, MANDOC_IDX, MAXPATHLEN); + xstrlcat(dst, MANDOC_IDX, MAXPATHLEN); + + if ( ! filecpy(dst, src)) + return(-1); + if (verbose) + printf("%s\n", dst); + + dst[(int)dsz] = src[(int)ssz] = '\0'; + + return(indexhtml(src, ssz, dst, dsz)); +} + +/* + * See if btree or recno databases in the destination are out of date + * with respect to a single manpath component. + * Return -1 on fatal error, 0 if the source is no longer valid (and + * shouldn't be listed), and 1 if the update went well. + */ +static int +treecpy(char *dst, char *src) +{ + size_t dsz, ssz; + int rc; + + dsz = strlen(dst); + ssz = strlen(src); + + xstrlcat(src, "/", MAXPATHLEN); + xstrlcat(dst, "/", MAXPATHLEN); + + xstrlcat(src, MANDOC_IDX, MAXPATHLEN); + xstrlcat(dst, MANDOC_IDX, MAXPATHLEN); + + if (-1 == (rc = isnewer(dst, src))) + return(0); + + dst[(int)dsz] = src[(int)ssz] = '\0'; + + if (1 == rc) + return(update(dst, src)); + + xstrlcat(src, "/", MAXPATHLEN); + xstrlcat(dst, "/", MAXPATHLEN); + + xstrlcat(src, MANDOC_DB, MAXPATHLEN); + xstrlcat(dst, MANDOC_DB, MAXPATHLEN); + + if (-1 == (rc = isnewer(dst, src))) + return(0); + else if (rc == 0) + return(1); + + dst[(int)dsz] = src[(int)ssz] = '\0'; + + return(update(dst, src)); +} + +/* + * Update the destination's file-tree with respect to changes in the + * source manpath components. + * "Change" is defined by an updated index or btree database. + * Returns 1 on success, 0 on failure. + */ +static int +manup(const struct manpaths *dirs, char *base) +{ + char dst[MAXPATHLEN], + src[MAXPATHLEN]; + const char *path; + int i, c; + size_t sz; + FILE *f; + + /* Create the path and file for the catman.conf file. */ + + sz = strlen(base); + xstrlcpy(dst, base, MAXPATHLEN); + xstrlcat(dst, "/etc", MAXPATHLEN); + if (-1 == mkpath(dst, 0755, 0755)) { + perror(dst); + return(0); + } + + xstrlcat(dst, "/catman.conf", MAXPATHLEN); + if (NULL == (f = fopen(dst, "w"))) { + perror(dst); + return(0); + } else if (verbose) + printf("%s\n", dst); + + for (i = 0; i < dirs->sz; i++) { + path = dirs->paths[i]; + dst[(int)sz] = '\0'; + xstrlcat(dst, path, MAXPATHLEN); + if (-1 == mkpath(dst, 0755, 0755)) { + perror(dst); + break; + } + + xstrlcpy(src, path, MAXPATHLEN); + if (-1 == (c = treecpy(dst, src))) + break; + else if (0 == c) + continue; + + /* + * We want to use a relative path here because manpath.h + * will realpath() when invoked with man.cgi, and we'll + * make sure to chdir() into the cache directory before. + * + * This allows the cache directory to be in an arbitrary + * place, working in both chroot() and non-chroot() + * "safe" modes. + */ + assert('/' == path[0]); + fprintf(f, "_whatdb %s/whatis.db\n", path + 1); + } + + fclose(f); + return(i == dirs->sz); +} + +/* + * Copyright (c) 1983, 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +static int +mkpath(char *path, mode_t mode, mode_t dir_mode) +{ + struct stat sb; + char *slash; + int done, exists; + + slash = path; + + for (;;) { + /* LINTED */ + slash += strspn(slash, "/"); + /* LINTED */ + slash += strcspn(slash, "/"); + + done = (*slash == '\0'); + *slash = '\0'; + + /* skip existing path components */ + exists = !stat(path, &sb); + if (!done && exists && S_ISDIR(sb.st_mode)) { + *slash = '/'; + continue; + } + + if (mkdir(path, done ? mode : dir_mode) == 0) { + if (mode > 0777 && chmod(path, mode) < 0) + return (-1); + } else { + if (!exists) { + /* Not there */ + return (-1); + } + if (!S_ISDIR(sb.st_mode)) { + /* Is there, but isn't a directory */ + errno = ENOTDIR; + return (-1); + } + } + + if (done) + break; + + *slash = '/'; + } + + return (0); +} diff --git a/cgi.c b/cgi.c new file mode 100644 index 000000000000..2f5870ff8efd --- /dev/null +++ b/cgi.c @@ -0,0 +1,1203 @@ +/* $Id: cgi.c,v 1.42 2012/03/24 01:46:25 kristaps Exp $ */ +/* + * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/param.h> +#include <sys/wait.h> + +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <dirent.h> +#include <fcntl.h> +#include <limits.h> +#include <regex.h> +#include <stdio.h> +#include <stdarg.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "apropos_db.h" +#include "mandoc.h" +#include "mdoc.h" +#include "man.h" +#include "main.h" +#include "manpath.h" +#include "mandocdb.h" + +#ifdef __linux__ +# include <db_185.h> +#else +# include <db.h> +#endif + +enum page { + PAGE_INDEX, + PAGE_SEARCH, + PAGE_SHOW, + PAGE__MAX +}; + +struct paths { + char *name; + char *path; +}; + +/* + * A query as passed to the search function. + */ +struct query { + const char *arch; /* architecture */ + const char *sec; /* manual section */ + const char *expr; /* unparsed expression string */ + int manroot; /* manroot index (or -1)*/ + int legacy; /* whether legacy mode */ +}; + +struct req { + struct query q; + struct paths *p; + size_t psz; + enum page page; +}; + +static int atou(const char *, unsigned *); +static void catman(const struct req *, const char *); +static int cmp(const void *, const void *); +static void format(const struct req *, const char *); +static void html_print(const char *); +static void html_printquery(const struct req *); +static void html_putchar(char); +static int http_decode(char *); +static void http_parse(struct req *, char *); +static void http_print(const char *); +static void http_putchar(char); +static void http_printquery(const struct req *); +static int pathstop(DIR *); +static void pathgen(DIR *, char *, struct req *); +static void pg_index(const struct req *, char *); +static void pg_search(const struct req *, char *); +static void pg_show(const struct req *, char *); +static void resp_bad(void); +static void resp_baddb(void); +static void resp_error400(void); +static void resp_error404(const char *); +static void resp_begin_html(int, const char *); +static void resp_begin_http(int, const char *); +static void resp_end_html(void); +static void resp_index(const struct req *); +static void resp_search(struct res *, size_t, void *); +static void resp_searchform(const struct req *); + +static const char *progname; /* cgi script name */ +static const char *cache; /* cache directory */ +static const char *css; /* css directory */ +static const char *host; /* hostname */ + +static const char * const pages[PAGE__MAX] = { + "index", /* PAGE_INDEX */ + "search", /* PAGE_SEARCH */ + "show", /* PAGE_SHOW */ +}; + +/* + * This is just OpenBSD's strtol(3) suggestion. + * I use it instead of strtonum(3) for portability's sake. + */ +static int +atou(const char *buf, unsigned *v) +{ + char *ep; + long lval; + + errno = 0; + lval = strtol(buf, &ep, 10); + if (buf[0] == '\0' || *ep != '\0') + return(0); + if ((errno == ERANGE && (lval == LONG_MAX || + lval == LONG_MIN)) || + (lval > INT_MAX || lval < 0)) + return(0); + + *v = (unsigned int)lval; + return(1); +} + +/* + * Print a character, escaping HTML along the way. + * This will pass non-ASCII straight to output: be warned! + */ +static void +html_putchar(char c) +{ + + switch (c) { + case ('"'): + printf(""e;"); + break; + case ('&'): + printf("&"); + break; + case ('>'): + printf(">"); + break; + case ('<'): + printf("<"); + break; + default: + putchar((unsigned char)c); + break; + } +} +static void +http_printquery(const struct req *req) +{ + + printf("&expr="); + http_print(req->q.expr ? req->q.expr : ""); + printf("&sec="); + http_print(req->q.sec ? req->q.sec : ""); + printf("&arch="); + http_print(req->q.arch ? req->q.arch : ""); +} + + +static void +html_printquery(const struct req *req) +{ + + printf("&expr="); + html_print(req->q.expr ? req->q.expr : ""); + printf("&sec="); + html_print(req->q.sec ? req->q.sec : ""); + printf("&arch="); + html_print(req->q.arch ? req->q.arch : ""); +} + +static void +http_print(const char *p) +{ + + if (NULL == p) + return; + while ('\0' != *p) + http_putchar(*p++); +} + +/* + * Call through to html_putchar(). + * Accepts NULL strings. + */ +static void +html_print(const char *p) +{ + + if (NULL == p) + return; + while ('\0' != *p) + html_putchar(*p++); +} + +/* + * Parse out key-value pairs from an HTTP request variable. + * This can be either a cookie or a POST/GET string, although man.cgi + * uses only GET for simplicity. + */ +static void +http_parse(struct req *req, char *p) +{ + char *key, *val, *manroot; + int i, legacy; + + memset(&req->q, 0, sizeof(struct query)); + + legacy = -1; + manroot = NULL; + + while ('\0' != *p) { + key = p; + val = NULL; + + p += (int)strcspn(p, ";&"); + if ('\0' != *p) + *p++ = '\0'; + if (NULL != (val = strchr(key, '='))) + *val++ = '\0'; + + if ('\0' == *key || NULL == val || '\0' == *val) + continue; + + /* Just abort handling. */ + + if ( ! http_decode(key)) + break; + if (NULL != val && ! http_decode(val)) + break; + + if (0 == strcmp(key, "expr")) + req->q.expr = val; + else if (0 == strcmp(key, "query")) + req->q.expr = val; + else if (0 == strcmp(key, "sec")) + req->q.sec = val; + else if (0 == strcmp(key, "sektion")) + req->q.sec = val; + else if (0 == strcmp(key, "arch")) + req->q.arch = val; + else if (0 == strcmp(key, "manpath")) + manroot = val; + else if (0 == strcmp(key, "apropos")) + legacy = 0 == strcmp(val, "0"); + } + + /* Test for old man.cgi compatibility mode. */ + + req->q.legacy = legacy > 0; + + /* + * Section "0" means no section when in legacy mode. + * For some man.cgi scripts, "default" arch is none. + */ + + if (req->q.legacy && NULL != req->q.sec) + if (0 == strcmp(req->q.sec, "0")) + req->q.sec = NULL; + if (req->q.legacy && NULL != req->q.arch) + if (0 == strcmp(req->q.arch, "default")) + req->q.arch = NULL; + + /* Default to first manroot. */ + + if (NULL != manroot) { + for (i = 0; i < (int)req->psz; i++) + if (0 == strcmp(req->p[i].name, manroot)) + break; + req->q.manroot = i < (int)req->psz ? i : -1; + } +} + +static void +http_putchar(char c) +{ + + if (isalnum((unsigned char)c)) { + putchar((unsigned char)c); + return; + } else if (' ' == c) { + putchar('+'); + return; + } + printf("%%%.2x", c); +} + +/* + * HTTP-decode a string. The standard explanation is that this turns + * "%4e+foo" into "n foo" in the regular way. This is done in-place + * over the allocated string. + */ +static int +http_decode(char *p) +{ + char hex[3]; + int c; + + hex[2] = '\0'; + + for ( ; '\0' != *p; p++) { + if ('%' == *p) { + if ('\0' == (hex[0] = *(p + 1))) + return(0); + if ('\0' == (hex[1] = *(p + 2))) + return(0); + if (1 != sscanf(hex, "%x", &c)) + return(0); + if ('\0' == c) + return(0); + + *p = (char)c; + memmove(p + 1, p + 3, strlen(p + 3) + 1); + } else + *p = '+' == *p ? ' ' : *p; + } + + *p = '\0'; + return(1); +} + +static void +resp_begin_http(int code, const char *msg) +{ + + if (200 != code) + printf("Status: %d %s\n", code, msg); + + puts("Content-Type: text/html; charset=utf-8\n" + "Cache-Control: no-cache\n" + "Pragma: no-cache\n" + ""); + + fflush(stdout); +} + +static void +resp_begin_html(int code, const char *msg) +{ + + resp_begin_http(code, msg); + + printf("<!DOCTYPE HTML PUBLIC " + " \"-//W3C//DTD HTML 4.01//EN\"" + " \"http://www.w3.org/TR/html4/strict.dtd\">\n" + "<HTML>\n" + "<HEAD>\n" + "<META HTTP-EQUIV=\"Content-Type\"" + " CONTENT=\"text/html; charset=utf-8\">\n" + "<LINK REL=\"stylesheet\" HREF=\"%s/man-cgi.css\"" + " TYPE=\"text/css\" media=\"all\">\n" + "<LINK REL=\"stylesheet\" HREF=\"%s/man.css\"" + " TYPE=\"text/css\" media=\"all\">\n" + "<TITLE>System Manpage Reference</TITLE>\n" + "</HEAD>\n" + "<BODY>\n" + "<!-- Begin page content. //-->\n", css, css); +} + +static void +resp_end_html(void) +{ + + puts("</BODY>\n" + "</HTML>"); +} + +static void +resp_searchform(const struct req *req) +{ + int i; + + puts("<!-- Begin search form. //-->"); + printf("<DIV ID=\"mancgi\">\n" + "<FORM ACTION=\"%s/search.html\" METHOD=\"get\">\n" + "<FIELDSET>\n" + "<LEGEND>Search Parameters</LEGEND>\n" + "<INPUT TYPE=\"submit\" " + " VALUE=\"Search\"> for manuals satisfying \n" + "<INPUT TYPE=\"text\" NAME=\"expr\" VALUE=\"", + progname); + html_print(req->q.expr ? req->q.expr : ""); + printf("\">, section " + "<INPUT TYPE=\"text\"" + " SIZE=\"4\" NAME=\"sec\" VALUE=\""); + html_print(req->q.sec ? req->q.sec : ""); + printf("\">, arch " + "<INPUT TYPE=\"text\"" + " SIZE=\"8\" NAME=\"arch\" VALUE=\""); + html_print(req->q.arch ? req->q.arch : ""); + printf("\">"); + if (req->psz > 1) { + puts(", <SELECT NAME=\"manpath\">"); + for (i = 0; i < (int)req->psz; i++) { + printf("<OPTION %s VALUE=\"", + (i == req->q.manroot) || + (0 == i && -1 == req->q.manroot) ? + "SELECTED=\"selected\"" : ""); + html_print(req->p[i].name); + printf("\">"); + html_print(req->p[i].name); + puts("</OPTION>"); + } + puts("</SELECT>"); + } + puts(".\n" + "<INPUT TYPE=\"reset\" VALUE=\"Reset\">\n" + "</FIELDSET>\n" + "</FORM>\n" + "</DIV>"); + puts("<!-- End search form. //-->"); +} + +static void +resp_index(const struct req *req) +{ + + resp_begin_html(200, NULL); + resp_searchform(req); + resp_end_html(); +} + +static void +resp_error400(void) +{ + + resp_begin_html(400, "Query Malformed"); + printf("<H1>Malformed Query</H1>\n" + "<P>\n" + "The query your entered was malformed.\n" + "Try again from the\n" + "<A HREF=\"%s/index.html\">main page</A>.\n" + "</P>", progname); + resp_end_html(); +} + +static void +resp_error404(const char *page) +{ + + resp_begin_html(404, "Not Found"); + puts("<H1>Page Not Found</H1>\n" + "<P>\n" + "The page you're looking for, "); + printf("<B>"); + html_print(page); + printf("</B>,\n" + "could not be found.\n" + "Try searching from the\n" + "<A HREF=\"%s/index.html\">main page</A>.\n" + "</P>", progname); + resp_end_html(); +} + +static void +resp_bad(void) +{ + resp_begin_html(500, "Internal Server Error"); + puts("<P>Generic badness happened.</P>"); + resp_end_html(); +} + +static void +resp_baddb(void) +{ + + resp_begin_html(500, "Internal Server Error"); + puts("<P>Your database is broken.</P>"); + resp_end_html(); +} + +static void +resp_search(struct res *r, size_t sz, void *arg) +{ + size_t i, matched; + const struct req *req; + + req = (const struct req *)arg; + + if (sz > 0) + assert(req->q.manroot >= 0); + + for (matched = i = 0; i < sz; i++) + if (r[i].matched) + matched++; + + if (1 == matched) { + for (i = 0; i < sz; i++) + if (r[i].matched) + break; + /* + * If we have just one result, then jump there now + * without any delay. + */ + puts("Status: 303 See Other"); + printf("Location: http://%s%s/show/%d/%u/%u.html?", + host, progname, req->q.manroot, + r[i].volume, r[i].rec); + http_printquery(req); + puts("\n" + "Content-Type: text/html; charset=utf-8\n"); + return; + } + + resp_begin_html(200, NULL); + resp_searchform(req); + + puts("<DIV CLASS=\"results\">"); + + if (0 == matched) { + puts("<P>\n" + "No results found.\n" + "</P>\n" + "</DIV>"); + resp_end_html(); + return; + } + + qsort(r, sz, sizeof(struct res), cmp); + + puts("<TABLE>"); + + for (i = 0; i < sz; i++) { + if ( ! r[i].matched) + continue; + printf("<TR>\n" + "<TD CLASS=\"title\">\n" + "<A HREF=\"%s/show/%d/%u/%u.html?", + progname, req->q.manroot, + r[i].volume, r[i].rec); + html_printquery(req); + printf("\">"); + html_print(r[i].title); + putchar('('); + html_print(r[i].cat); + if (r[i].arch && '\0' != *r[i].arch) { + putchar('/'); + html_print(r[i].arch); + } + printf(")</A>\n" + "</TD>\n" + "<TD CLASS=\"desc\">"); + html_print(r[i].desc); + puts("</TD>\n" + "</TR>"); + } + + puts("</TABLE>\n" + "</DIV>"); + resp_end_html(); +} + +/* ARGSUSED */ +static void +pg_index(const struct req *req, char *path) +{ + + resp_index(req); +} + +static void +catman(const struct req *req, const char *file) +{ + FILE *f; + size_t len; + int i; + char *p; + int italic, bold; + + if (NULL == (f = fopen(file, "r"))) { + resp_baddb(); + return; + } + + resp_begin_html(200, NULL); + resp_searchform(req); + puts("<DIV CLASS=\"catman\">\n" + "<PRE>"); + + while (NULL != (p = fgetln(f, &len))) { + bold = italic = 0; + for (i = 0; i < (int)len - 1; i++) { + /* + * This means that the catpage is out of state. + * Ignore it and keep going (although the + * catpage is bogus). + */ + + if ('\b' == p[i] || '\n' == p[i]) + continue; + + /* + * Print a regular character. + * Close out any bold/italic scopes. + * If we're in back-space mode, make sure we'll + * have something to enter when we backspace. + */ + + if ('\b' != p[i + 1]) { + if (italic) + printf("</I>"); + if (bold) + printf("</B>"); + italic = bold = 0; + html_putchar(p[i]); + continue; + } else if (i + 2 >= (int)len) + continue; + + /* Italic mode. */ + + if ('_' == p[i]) { + if (bold) + printf("</B>"); + if ( ! italic) + printf("<I>"); + bold = 0; + italic = 1; + i += 2; + html_putchar(p[i]); + continue; + } + + /* + * Handle funny behaviour troff-isms. + * These grok'd from the original man2html.c. + */ + + if (('+' == p[i] && 'o' == p[i + 2]) || + ('o' == p[i] && '+' == p[i + 2]) || + ('|' == p[i] && '=' == p[i + 2]) || + ('=' == p[i] && '|' == p[i + 2]) || + ('*' == p[i] && '=' == p[i + 2]) || + ('=' == p[i] && '*' == p[i + 2]) || + ('*' == p[i] && '|' == p[i + 2]) || + ('|' == p[i] && '*' == p[i + 2])) { + if (italic) + printf("</I>"); + if (bold) + printf("</B>"); + italic = bold = 0; + putchar('*'); + i += 2; + continue; + } else if (('|' == p[i] && '-' == p[i + 2]) || + ('-' == p[i] && '|' == p[i + 1]) || + ('+' == p[i] && '-' == p[i + 1]) || + ('-' == p[i] && '+' == p[i + 1]) || + ('+' == p[i] && '|' == p[i + 1]) || + ('|' == p[i] && '+' == p[i + 1])) { + if (italic) + printf("</I>"); + if (bold) + printf("</B>"); + italic = bold = 0; + putchar('+'); + i += 2; + continue; + } + + /* Bold mode. */ + + if (italic) + printf("</I>"); + if ( ! bold) + printf("<B>"); + bold = 1; + italic = 0; + i += 2; + html_putchar(p[i]); + } + + /* + * Clean up the last character. + * We can get to a newline; don't print that. + */ + + if (italic) + printf("</I>"); + if (bold) + printf("</B>"); + + if (i == (int)len - 1 && '\n' != p[i]) + html_putchar(p[i]); + + putchar('\n'); + } + + puts("</PRE>\n" + "</DIV>\n" + "</BODY>\n" + "</HTML>"); + + fclose(f); +} + +static void +format(const struct req *req, const char *file) +{ + struct mparse *mp; + int fd; + struct mdoc *mdoc; + struct man *man; + void *vp; + enum mandoclevel rc; + char opts[MAXPATHLEN + 128]; + + if (-1 == (fd = open(file, O_RDONLY, 0))) { + resp_baddb(); + return; + } + + mp = mparse_alloc(MPARSE_AUTO, MANDOCLEVEL_FATAL, NULL, NULL); + rc = mparse_readfd(mp, fd, file); + close(fd); + + if (rc >= MANDOCLEVEL_FATAL) { + resp_baddb(); + return; + } + + snprintf(opts, sizeof(opts), "fragment," + "man=%s/search.html?sec=%%S&expr=%%N," + /*"includes=/cgi-bin/man.cgi/usr/include/%%I"*/, + progname); + + mparse_result(mp, &mdoc, &man); + if (NULL == man && NULL == mdoc) { + resp_baddb(); + mparse_free(mp); + return; + } + + resp_begin_html(200, NULL); + resp_searchform(req); + + vp = html_alloc(opts); + + if (NULL != mdoc) + html_mdoc(vp, mdoc); + else + html_man(vp, man); + + puts("</BODY>\n" + "</HTML>"); + + html_free(vp); + mparse_free(mp); +} + +static void +pg_show(const struct req *req, char *path) +{ + struct manpaths ps; + size_t sz; + char *sub; + char file[MAXPATHLEN]; + const char *cp; + int rc, catm; + unsigned int vol, rec, mr; + DB *idx; + DBT key, val; + + idx = NULL; + + /* Parse out mroot, volume, and record from the path. */ + + if (NULL == path || NULL == (sub = strchr(path, '/'))) { + resp_error400(); + return; + } + *sub++ = '\0'; + if ( ! atou(path, &mr)) { + resp_error400(); + return; + } + path = sub; + if (NULL == (sub = strchr(path, '/'))) { + resp_error400(); + return; + } + *sub++ = '\0'; + if ( ! atou(path, &vol) || ! atou(sub, &rec)) { + resp_error400(); + return; + } else if (mr >= (unsigned int)req->psz) { + resp_error400(); + return; + } + + /* + * Begin by chdir()ing into the manroot. + * This way we can pick up the database files, which are + * relative to the manpath root. + */ + + if (-1 == chdir(req->p[(int)mr].path)) { + perror(req->p[(int)mr].path); + resp_baddb(); + return; + } + + memset(&ps, 0, sizeof(struct manpaths)); + manpath_manconf(&ps, "etc/catman.conf"); + + if (vol >= (unsigned int)ps.sz) { + resp_error400(); + goto out; + } + + sz = strlcpy(file, ps.paths[vol], MAXPATHLEN); + assert(sz < MAXPATHLEN); + strlcat(file, "/", MAXPATHLEN); + strlcat(file, MANDOC_IDX, MAXPATHLEN); + + /* Open the index recno(3) database. */ + + idx = dbopen(file, O_RDONLY, 0, DB_RECNO, NULL); + if (NULL == idx) { + perror(file); + resp_baddb(); + goto out; + } + + key.data = &rec; + key.size = 4; + + if (0 != (rc = (*idx->get)(idx, &key, &val, 0))) { + rc < 0 ? resp_baddb() : resp_error400(); + goto out; + } else if (0 == val.size) { + resp_baddb(); + goto out; + } + + cp = (char *)val.data; + catm = 'c' == *cp++; + + if (NULL == memchr(cp, '\0', val.size - 1)) + resp_baddb(); + else { + file[(int)sz] = '\0'; + strlcat(file, "/", MAXPATHLEN); + strlcat(file, cp, MAXPATHLEN); + if (catm) + catman(req, file); + else + format(req, file); + } +out: + if (idx) + (*idx->close)(idx); + manpath_free(&ps); +} + +static void +pg_search(const struct req *req, char *path) +{ + size_t tt, ressz; + struct manpaths ps; + int i, sz, rc; + const char *ep, *start; + struct res *res; + char **cp; + struct opts opt; + struct expr *expr; + + if (req->q.manroot < 0 || 0 == req->psz) { + resp_search(NULL, 0, (void *)req); + return; + } + + memset(&opt, 0, sizeof(struct opts)); + + ep = req->q.expr; + opt.arch = req->q.arch; + opt.cat = req->q.sec; + rc = -1; + sz = 0; + cp = NULL; + ressz = 0; + res = NULL; + + /* + * Begin by chdir()ing into the root of the manpath. + * This way we can pick up the database files, which are + * relative to the manpath root. + */ + + assert(req->q.manroot < (int)req->psz); + if (-1 == (chdir(req->p[req->q.manroot].path))) { + perror(req->p[req->q.manroot].path); + resp_search(NULL, 0, (void *)req); + return; + } + + memset(&ps, 0, sizeof(struct manpaths)); + manpath_manconf(&ps, "etc/catman.conf"); + + /* + * Poor man's tokenisation: just break apart by spaces. + * Yes, this is half-ass. But it works for now. + */ + + while (ep && isspace((unsigned char)*ep)) + ep++; + + while (ep && '\0' != *ep) { + cp = mandoc_realloc(cp, (sz + 1) * sizeof(char *)); + start = ep; + while ('\0' != *ep && ! isspace((unsigned char)*ep)) + ep++; + cp[sz] = mandoc_malloc((ep - start) + 1); + memcpy(cp[sz], start, ep - start); + cp[sz++][ep - start] = '\0'; + while (isspace((unsigned char)*ep)) + ep++; + } + + /* + * Pump down into apropos backend. + * The resp_search() function is called with the results. + */ + + expr = req->q.legacy ? + termcomp(sz, cp, &tt) : exprcomp(sz, cp, &tt); + + if (NULL != expr) + rc = apropos_search + (ps.sz, ps.paths, &opt, expr, tt, + (void *)req, &ressz, &res, resp_search); + + /* ...unless errors occured. */ + + if (0 == rc) + resp_baddb(); + else if (-1 == rc) + resp_search(NULL, 0, NULL); + + for (i = 0; i < sz; i++) + free(cp[i]); + + free(cp); + resfree(res, ressz); + exprfree(expr); + manpath_free(&ps); +} + +int +main(void) +{ + int i; + char buf[MAXPATHLEN]; + DIR *cwd; + struct req req; + char *p, *path, *subpath; + + /* Scan our run-time environment. */ + + if (NULL == (cache = getenv("CACHE_DIR"))) + cache = "/cache/man.cgi"; + + if (NULL == (progname = getenv("SCRIPT_NAME"))) + progname = ""; + + if (NULL == (css = getenv("CSS_DIR"))) + css = ""; + + if (NULL == (host = getenv("HTTP_HOST"))) + host = "localhost"; + + /* + * First we change directory into the cache directory so that + * subsequent scanning for manpath directories is rooted + * relative to the same position. + */ + + if (-1 == chdir(cache)) { + perror(cache); + resp_bad(); + return(EXIT_FAILURE); + } else if (NULL == (cwd = opendir(cache))) { + perror(cache); + resp_bad(); + return(EXIT_FAILURE); + } + + memset(&req, 0, sizeof(struct req)); + + strlcpy(buf, ".", MAXPATHLEN); + pathgen(cwd, buf, &req); + closedir(cwd); + + /* Next parse out the query string. */ + + if (NULL != (p = getenv("QUERY_STRING"))) + http_parse(&req, p); + + /* + * Now juggle paths to extract information. + * We want to extract our filetype (the file suffix), the + * initial path component, then the trailing component(s). + * Start with leading subpath component. + */ + + subpath = path = NULL; + req.page = PAGE__MAX; + + if (NULL == (path = getenv("PATH_INFO")) || '\0' == *path) + req.page = PAGE_INDEX; + + if (NULL != path && '/' == *path && '\0' == *++path) + req.page = PAGE_INDEX; + + /* Strip file suffix. */ + + if (NULL != path && NULL != (p = strrchr(path, '.'))) + if (NULL != p && NULL == strchr(p, '/')) + *p++ = '\0'; + + /* Resolve subpath component. */ + + if (NULL != path && NULL != (subpath = strchr(path, '/'))) + *subpath++ = '\0'; + + /* Map path into one we recognise. */ + + if (NULL != path && '\0' != *path) + for (i = 0; i < (int)PAGE__MAX; i++) + if (0 == strcmp(pages[i], path)) { + req.page = (enum page)i; + break; + } + + /* Route pages. */ + + switch (req.page) { + case (PAGE_INDEX): + pg_index(&req, subpath); + break; + case (PAGE_SEARCH): + pg_search(&req, subpath); + break; + case (PAGE_SHOW): + pg_show(&req, subpath); + break; + default: + resp_error404(path); + break; + } + + for (i = 0; i < (int)req.psz; i++) { + free(req.p[i].path); + free(req.p[i].name); + } + + free(req.p); + return(EXIT_SUCCESS); +} + +static int +cmp(const void *p1, const void *p2) +{ + + return(strcasecmp(((const struct res *)p1)->title, + ((const struct res *)p2)->title)); +} + +/* + * Check to see if an "etc" path consists of a catman.conf file. If it + * does, that means that the path contains a tree created by catman(8) + * and should be used for indexing. + */ +static int +pathstop(DIR *dir) +{ + struct dirent *d; + + while (NULL != (d = readdir(dir))) + if (DT_REG == d->d_type) + if (0 == strcmp(d->d_name, "catman.conf")) + return(1); + + return(0); +} + +/* + * Scan for indexable paths. + * This adds all paths with "etc/catman.conf" to the buffer. + */ +static void +pathgen(DIR *dir, char *path, struct req *req) +{ + struct dirent *d; + char *cp; + DIR *cd; + int rc; + size_t sz, ssz; + + sz = strlcat(path, "/", MAXPATHLEN); + if (sz >= MAXPATHLEN) { + fprintf(stderr, "%s: Path too long", path); + return; + } + + /* + * First, scan for the "etc" directory. + * If it's found, then see if it should cause us to stop. This + * happens when a catman.conf is found in the directory. + */ + + rc = 0; + while (0 == rc && NULL != (d = readdir(dir))) { + if (DT_DIR != d->d_type || strcmp(d->d_name, "etc")) + continue; + + path[(int)sz] = '\0'; + ssz = strlcat(path, d->d_name, MAXPATHLEN); + + if (ssz >= MAXPATHLEN) { + fprintf(stderr, "%s: Path too long", path); + return; + } else if (NULL == (cd = opendir(path))) { + perror(path); + return; + } + + rc = pathstop(cd); + closedir(cd); + } + + if (rc > 0) { + /* This also strips the trailing slash. */ + path[(int)--sz] = '\0'; + req->p = mandoc_realloc + (req->p, + (req->psz + 1) * sizeof(struct paths)); + /* + * Strip out the leading "./" unless we're just a ".", + * in which case use an empty string as our name. + */ + req->p[(int)req->psz].path = mandoc_strdup(path); + req->p[(int)req->psz].name = + cp = mandoc_strdup(path + (1 == sz ? 1 : 2)); + req->psz++; + /* + * The name is just the path with all the slashes taken + * out of it. Simple but effective. + */ + for ( ; '\0' != *cp; cp++) + if ('/' == *cp) + *cp = ' '; + return; + } + + /* + * If no etc/catman.conf was found, recursively enter child + * directory and continue scanning. + */ + + rewinddir(dir); + while (NULL != (d = readdir(dir))) { + if (DT_DIR != d->d_type || '.' == d->d_name[0]) + continue; + + path[(int)sz] = '\0'; + ssz = strlcat(path, d->d_name, MAXPATHLEN); + + if (ssz >= MAXPATHLEN) { + fprintf(stderr, "%s: Path too long", path); + return; + } else if (NULL == (cd = opendir(path))) { + perror(path); + return; + } + + pathgen(cd, path, req); + closedir(cd); + } +} diff --git a/chars.c b/chars.c new file mode 100644 index 000000000000..ce03347b5d83 --- /dev/null +++ b/chars.c @@ -0,0 +1,167 @@ +/* $Id: chars.c,v 1.52 2011/11/08 00:15:23 kristaps Exp $ */ +/* + * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <assert.h> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> + +#include "mandoc.h" +#include "libmandoc.h" + +#define PRINT_HI 126 +#define PRINT_LO 32 + +struct ln { + struct ln *next; + const char *code; + const char *ascii; + int unicode; +}; + +#define LINES_MAX 328 + +#define CHAR(in, ch, code) \ + { NULL, (in), (ch), (code) }, + +#define CHAR_TBL_START static struct ln lines[LINES_MAX] = { +#define CHAR_TBL_END }; + +#include "chars.in" + +struct mchars { + struct ln **htab; +}; + +static const struct ln *find(const struct mchars *, + const char *, size_t); + +void +mchars_free(struct mchars *arg) +{ + + free(arg->htab); + free(arg); +} + +struct mchars * +mchars_alloc(void) +{ + struct mchars *tab; + struct ln **htab; + struct ln *pp; + int i, hash; + + /* + * Constructs a very basic chaining hashtable. The hash routine + * is simply the integral value of the first character. + * Subsequent entries are chained in the order they're processed. + */ + + tab = mandoc_malloc(sizeof(struct mchars)); + htab = mandoc_calloc(PRINT_HI - PRINT_LO + 1, sizeof(struct ln **)); + + for (i = 0; i < LINES_MAX; i++) { + hash = (int)lines[i].code[0] - PRINT_LO; + + if (NULL == (pp = htab[hash])) { + htab[hash] = &lines[i]; + continue; + } + + for ( ; pp->next; pp = pp->next) + /* Scan ahead. */ ; + pp->next = &lines[i]; + } + + tab->htab = htab; + return(tab); +} + +int +mchars_spec2cp(const struct mchars *arg, const char *p, size_t sz) +{ + const struct ln *ln; + + ln = find(arg, p, sz); + if (NULL == ln) + return(-1); + return(ln->unicode); +} + +char +mchars_num2char(const char *p, size_t sz) +{ + int i; + + if ((i = mandoc_strntoi(p, sz, 10)) < 0) + return('\0'); + return(i > 0 && i < 256 && isprint(i) ? + /* LINTED */ i : '\0'); +} + +int +mchars_num2uc(const char *p, size_t sz) +{ + int i; + + if ((i = mandoc_strntoi(p, sz, 16)) < 0) + return('\0'); + /* FIXME: make sure we're not in a bogus range. */ + return(i > 0x80 && i <= 0x10FFFF ? i : '\0'); +} + +const char * +mchars_spec2str(const struct mchars *arg, + const char *p, size_t sz, size_t *rsz) +{ + const struct ln *ln; + + ln = find(arg, p, sz); + if (NULL == ln) { + *rsz = 1; + return(NULL); + } + + *rsz = strlen(ln->ascii); + return(ln->ascii); +} + +static const struct ln * +find(const struct mchars *tab, const char *p, size_t sz) +{ + const struct ln *pp; + int hash; + + assert(p); + + if (0 == sz || p[0] < PRINT_LO || p[0] > PRINT_HI) + return(NULL); + + hash = (int)p[0] - PRINT_LO; + + for (pp = tab->htab[hash]; pp; pp = pp->next) + if (0 == strncmp(pp->code, p, sz) && + '\0' == pp->code[(int)sz]) + return(pp); + + return(NULL); +} diff --git a/chars.in b/chars.in new file mode 100644 index 000000000000..a4c45b3c43eb --- /dev/null +++ b/chars.in @@ -0,0 +1,397 @@ +/* $Id: chars.in,v 1.42 2011/10/02 10:02:26 kristaps Exp $ */ +/* + * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * The ASCII translation tables. + * + * The left-hand side corresponds to the input sequence (\x, \(xx, \*(xx + * and so on) whose length is listed second element. The right-hand + * side is what's produced by the front-end, with the fourth element + * being its length. + * + * XXX - C-escape strings! + * XXX - update LINES_MAX if adding more! + */ + +/* Non-breaking, non-collapsing space uses unit separator. */ +static const char ascii_nbrsp[2] = { ASCII_NBRSP, '\0' }; + +CHAR_TBL_START + +/* Spacing. */ +CHAR("c", "", 0) +CHAR("0", " ", 8194) +CHAR(" ", ascii_nbrsp, 160) +CHAR("~", ascii_nbrsp, 160) +CHAR("%", "", 0) +CHAR("&", "", 0) +CHAR("^", "", 0) +CHAR("|", "", 0) +CHAR("}", "", 0) + +/* Accents. */ +CHAR("a\"", "\"", 779) +CHAR("a-", "-", 175) +CHAR("a.", ".", 729) +CHAR("a^", "^", 770) +CHAR("\'", "\'", 769) +CHAR("aa", "\'", 769) +CHAR("ga", "`", 768) +CHAR("`", "`", 768) +CHAR("ab", "`", 774) +CHAR("ac", ",", 807) +CHAR("ad", "\"", 776) +CHAR("ah", "v", 711) +CHAR("ao", "o", 730) +CHAR("a~", "~", 771) +CHAR("ho", ",", 808) +CHAR("ha", "^", 94) +CHAR("ti", "~", 126) + +/* Quotes. */ +CHAR("Bq", ",,", 8222) +CHAR("bq", ",", 8218) +CHAR("lq", "``", 8220) +CHAR("rq", "\'\'", 8221) +CHAR("oq", "`", 8216) +CHAR("cq", "\'", 8217) +CHAR("aq", "\'", 39) +CHAR("dq", "\"", 34) +CHAR("Fo", "<<", 171) +CHAR("Fc", ">>", 187) +CHAR("fo", "<", 8249) +CHAR("fc", ">", 8250) + +/* Brackets. */ +CHAR("lB", "[", 91) +CHAR("rB", "]", 93) +CHAR("lC", "{", 123) +CHAR("rC", "}", 125) +CHAR("la", "<", 60) +CHAR("ra", ">", 62) +CHAR("bv", "|", 9130) +CHAR("braceex", "|", 9130) +CHAR("bracketlefttp", "|", 9121) +CHAR("bracketleftbp", "|", 9123) +CHAR("bracketleftex", "|", 9122) +CHAR("bracketrighttp", "|", 9124) +CHAR("bracketrightbp", "|", 9126) +CHAR("bracketrightex", "|", 9125) +CHAR("lt", ",-", 9127) +CHAR("bracelefttp", ",-", 9127) +CHAR("lk", "{", 9128) +CHAR("braceleftmid", "{", 9128) +CHAR("lb", ",-", 9129) +CHAR("braceleftbp", "`-", 9129) +CHAR("braceleftex", "|", 9130) +CHAR("rt", "-.", 9131) +CHAR("bracerighttp", "-.", 9131) +CHAR("rk", "}", 9132) +CHAR("bracerightmid", "}", 9132) +CHAR("rb", "-\'", 9133) +CHAR("bracerightbp", "-\'", 9133) +CHAR("bracerightex", "|", 9130) +CHAR("parenlefttp", "/", 9115) +CHAR("parenleftbp", "\\", 9117) +CHAR("parenleftex", "|", 9116) +CHAR("parenrighttp", "\\", 9118) +CHAR("parenrightbp", "/", 9120) +CHAR("parenrightex", "|", 9119) + +/* Greek characters. */ +CHAR("*A", "A", 913) +CHAR("*B", "B", 914) +CHAR("*G", "|", 915) +CHAR("*D", "/\\", 916) +CHAR("*E", "E", 917) +CHAR("*Z", "Z", 918) +CHAR("*Y", "H", 919) +CHAR("*H", "O", 920) +CHAR("*I", "I", 921) +CHAR("*K", "K", 922) +CHAR("*L", "/\\", 923) +CHAR("*M", "M", 924) +CHAR("*N", "N", 925) +CHAR("*C", "H", 926) +CHAR("*O", "O", 927) +CHAR("*P", "TT", 928) +CHAR("*R", "P", 929) +CHAR("*S", ">", 931) +CHAR("*T", "T", 932) +CHAR("*U", "Y", 933) +CHAR("*F", "O_", 934) +CHAR("*X", "X", 935) +CHAR("*Q", "Y", 936) +CHAR("*W", "O", 937) +CHAR("*a", "a", 945) +CHAR("*b", "B", 946) +CHAR("*g", "y", 947) +CHAR("*d", "d", 948) +CHAR("*e", "e", 949) +CHAR("*z", "C", 950) +CHAR("*y", "n", 951) +CHAR("*h", "0", 952) +CHAR("*i", "i", 953) +CHAR("*k", "k", 954) +CHAR("*l", "\\", 955) +CHAR("*m", "u", 956) +CHAR("*n", "v", 957) +CHAR("*c", "E", 958) +CHAR("*o", "o", 959) +CHAR("*p", "n", 960) +CHAR("*r", "p", 961) +CHAR("*s", "o", 963) +CHAR("*t", "t", 964) +CHAR("*u", "u", 965) +CHAR("*f", "o", 981) +CHAR("*x", "x", 967) +CHAR("*q", "u", 968) +CHAR("*w", "w", 969) +CHAR("+h", "0", 977) +CHAR("+f", "o", 966) +CHAR("+p", "w", 982) +CHAR("+e", "e", 1013) +CHAR("ts", "s", 962) + +/* Accented letters. */ +CHAR(",C", "C", 199) +CHAR(",c", "c", 231) +CHAR("/L", "L", 321) +CHAR("/O", "O", 216) +CHAR("/l", "l", 322) +CHAR("/o", "o", 248) +CHAR("oA", "A", 197) +CHAR("oa", "a", 229) +CHAR(":A", "A", 196) +CHAR(":E", "E", 203) +CHAR(":I", "I", 207) +CHAR(":O", "O", 214) +CHAR(":U", "U", 220) +CHAR(":a", "a", 228) +CHAR(":e", "e", 235) +CHAR(":i", "i", 239) +CHAR(":o", "o", 246) +CHAR(":u", "u", 252) +CHAR(":y", "y", 255) +CHAR("\'A", "A", 193) +CHAR("\'E", "E", 201) +CHAR("\'I", "I", 205) +CHAR("\'O", "O", 211) +CHAR("\'U", "U", 218) +CHAR("\'a", "a", 225) +CHAR("\'e", "e", 233) +CHAR("\'i", "i", 237) +CHAR("\'o", "o", 243) +CHAR("\'u", "u", 250) +CHAR("^A", "A", 194) +CHAR("^E", "E", 202) +CHAR("^I", "I", 206) +CHAR("^O", "O", 212) +CHAR("^U", "U", 219) +CHAR("^a", "a", 226) +CHAR("^e", "e", 234) +CHAR("^i", "i", 238) +CHAR("^o", "o", 244) +CHAR("^u", "u", 251) +CHAR("`A", "A", 192) +CHAR("`E", "E", 200) +CHAR("`I", "I", 204) +CHAR("`O", "O", 210) +CHAR("`U", "U", 217) +CHAR("`a", "a", 224) +CHAR("`e", "e", 232) +CHAR("`i", "i", 236) +CHAR("`o", "o", 242) +CHAR("`u", "u", 249) +CHAR("~A", "A", 195) +CHAR("~N", "N", 209) +CHAR("~O", "O", 213) +CHAR("~a", "a", 227) +CHAR("~n", "n", 241) +CHAR("~o", "o", 245) + +/* Arrows and lines. */ +CHAR("<-", "<-", 8592) +CHAR("->", "->", 8594) +CHAR("<>", "<>", 8596) +CHAR("da", "v", 8595) +CHAR("ua", "^", 8593) +CHAR("va", "^v", 8597) +CHAR("lA", "<=", 8656) +CHAR("rA", "=>", 8658) +CHAR("hA", "<=>", 8660) +CHAR("dA", "v", 8659) +CHAR("uA", "^", 8657) +CHAR("vA", "^=v", 8661) + +/* Logic. */ +CHAR("AN", "^", 8743) +CHAR("OR", "v", 8744) +CHAR("no", "~", 172) +CHAR("tno", "~", 172) +CHAR("te", "3", 8707) +CHAR("fa", "V", 8704) +CHAR("st", "-)", 8715) +CHAR("tf", ".:.", 8756) +CHAR("3d", ".:.", 8756) +CHAR("or", "|", 124) + +/* Mathematicals. */ +CHAR("pl", "+", 43) +CHAR("mi", "-", 8722) +CHAR("-", "-", 45) +CHAR("-+", "-+", 8723) +CHAR("+-", "+-", 177) +CHAR("t+-", "+-", 177) +CHAR("pc", ".", 183) +CHAR("md", ".", 8901) +CHAR("mu", "x", 215) +CHAR("tmu", "x", 215) +CHAR("c*", "x", 8855) +CHAR("c+", "+", 8853) +CHAR("di", "-:-", 247) +CHAR("tdi", "-:-", 247) +CHAR("f/", "/", 8260) +CHAR("**", "*", 8727) +CHAR("<=", "<=", 8804) +CHAR(">=", ">=", 8805) +CHAR("<<", "<<", 8810) +CHAR(">>", ">>", 8811) +CHAR("eq", "=", 61) +CHAR("!=", "!=", 8800) +CHAR("==", "==", 8801) +CHAR("ne", "!==", 8802) +CHAR("=~", "=~", 8773) +CHAR("-~", "-~", 8771) +CHAR("ap", "~", 8764) +CHAR("~~", "~~", 8776) +CHAR("~=", "~=", 8780) +CHAR("pt", "oc", 8733) +CHAR("es", "{}", 8709) +CHAR("mo", "E", 8712) +CHAR("nm", "!E", 8713) +CHAR("sb", "(=", 8834) +CHAR("nb", "(!=", 8836) +CHAR("sp", "=)", 8835) +CHAR("nc", "!=)", 8837) +CHAR("ib", "(=", 8838) +CHAR("ip", "=)", 8839) +CHAR("ca", "(^)", 8745) +CHAR("cu", "U", 8746) +CHAR("/_", "/_", 8736) +CHAR("pp", "_|_", 8869) +CHAR("is", "I", 8747) +CHAR("integral", "I", 8747) +CHAR("sum", "E", 8721) +CHAR("product", "TT", 8719) +CHAR("coproduct", "U", 8720) +CHAR("gr", "V", 8711) +CHAR("sr", "\\/", 8730) +CHAR("sqrt", "\\/", 8730) +CHAR("lc", "|~", 8968) +CHAR("rc", "~|", 8969) +CHAR("lf", "|_", 8970) +CHAR("rf", "_|", 8971) +CHAR("if", "oo", 8734) +CHAR("Ah", "N", 8501) +CHAR("Im", "I", 8465) +CHAR("Re", "R", 8476) +CHAR("pd", "a", 8706) +CHAR("-h", "/h", 8463) +CHAR("12", "1/2", 189) +CHAR("14", "1/4", 188) +CHAR("34", "3/4", 190) + +/* Ligatures. */ +CHAR("ff", "ff", 64256) +CHAR("fi", "fi", 64257) +CHAR("fl", "fl", 64258) +CHAR("Fi", "ffi", 64259) +CHAR("Fl", "ffl", 64260) +CHAR("AE", "AE", 198) +CHAR("ae", "ae", 230) +CHAR("OE", "OE", 338) +CHAR("oe", "oe", 339) +CHAR("ss", "ss", 223) +CHAR("IJ", "IJ", 306) +CHAR("ij", "ij", 307) + +/* Special letters. */ +CHAR("-D", "D", 208) +CHAR("Sd", "o", 240) +CHAR("TP", "b", 222) +CHAR("Tp", "b", 254) +CHAR(".i", "i", 305) +CHAR(".j", "j", 567) + +/* Currency. */ +CHAR("Do", "$", 36) +CHAR("ct", "c", 162) +CHAR("Eu", "EUR", 8364) +CHAR("eu", "EUR", 8364) +CHAR("Ye", "Y", 165) +CHAR("Po", "L", 163) +CHAR("Cs", "x", 164) +CHAR("Fn", "f", 402) + +/* Lines. */ +CHAR("ba", "|", 124) +CHAR("br", "|", 9474) +CHAR("ul", "_", 95) +CHAR("rl", "-", 8254) +CHAR("bb", "|", 166) +CHAR("sl", "/", 47) +CHAR("rs", "\\", 92) + +/* Text markers. */ +CHAR("ci", "o", 9675) +CHAR("bu", "o", 8226) +CHAR("dd", "=", 8225) +CHAR("dg", "-", 8224) +CHAR("lz", "<>", 9674) +CHAR("sq", "[]", 9633) +CHAR("ps", "9|", 182) +CHAR("sc", "S", 167) +CHAR("lh", "<=", 9756) +CHAR("rh", "=>", 9758) +CHAR("at", "@", 64) +CHAR("sh", "#", 35) +CHAR("CR", "_|", 8629) +CHAR("OK", "\\/", 10003) + +/* Legal symbols. */ +CHAR("co", "(C)", 169) +CHAR("rg", "(R)", 174) +CHAR("tm", "tm", 8482) + +/* Punctuation. */ +CHAR(".", ".", 46) +CHAR("r!", "i", 161) +CHAR("r?", "c", 191) +CHAR("em", "--", 8212) +CHAR("en", "-", 8211) +CHAR("hy", "-", 8208) +CHAR("e", "\\", 92) + +/* Units. */ +CHAR("de", "o", 176) +CHAR("%0", "%o", 8240) +CHAR("fm", "\'", 8242) +CHAR("sd", "\"", 8243) +CHAR("mc", "mu", 181) + +CHAR_TBL_END diff --git a/compat_fgetln.c b/compat_fgetln.c new file mode 100644 index 000000000000..49c9985b6e00 --- /dev/null +++ b/compat_fgetln.c @@ -0,0 +1,93 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_FGETLN + +int dummy; + +#else + +/* $NetBSD: fgetln.c,v 1.3 2006/09/25 07:18:17 lukem Exp $ */ + +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Christos Zoulas. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +char * +fgetln(fp, len) + FILE *fp; + size_t *len; +{ + static char *buf = NULL; + static size_t bufsiz = 0; + char *ptr; + + + if (buf == NULL) { + bufsiz = BUFSIZ; + if ((buf = malloc(bufsiz)) == NULL) + return NULL; + } + + if (fgets(buf, bufsiz, fp) == NULL) + return NULL; + + *len = 0; + while ((ptr = strchr(&buf[*len], '\n')) == NULL) { + size_t nbufsiz = bufsiz + BUFSIZ; + char *nbuf = realloc(buf, nbufsiz); + + if (nbuf == NULL) { + int oerrno = errno; + free(buf); + errno = oerrno; + buf = NULL; + return NULL; + } else + buf = nbuf; + + *len = bufsiz; + if (fgets(&buf[bufsiz], BUFSIZ, fp) == NULL) + return buf; + + bufsiz = nbufsiz; + } + + *len = (ptr - buf) + 1; + return buf; +} + +#endif diff --git a/compat_getsubopt.c b/compat_getsubopt.c new file mode 100644 index 000000000000..9cd415367dad --- /dev/null +++ b/compat_getsubopt.c @@ -0,0 +1,104 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_GETSUBOPT + +int dummy; + +#else + +/* $OpenBSD: getsubopt.c,v 1.4 2005/08/08 08:05:36 espie Exp $ */ + +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <unistd.h> +#include <stdlib.h> +#include <string.h> + +/* + * The SVID interface to getsubopt provides no way of figuring out which + * part of the suboptions list wasn't matched. This makes error messages + * tricky... The extern variable suboptarg is a pointer to the token + * which didn't match. + */ +char *suboptarg; + +int +getsubopt(char **optionp, char * const *tokens, char **valuep) +{ + int cnt; + char *p; + + suboptarg = *valuep = NULL; + + if (!optionp || !*optionp) + return(-1); + + /* skip leading white-space, commas */ + for (p = *optionp; *p && (*p == ',' || *p == ' ' || *p == '\t'); ++p); + + if (!*p) { + *optionp = p; + return(-1); + } + + /* save the start of the token, and skip the rest of the token. */ + for (suboptarg = p; + *++p && *p != ',' && *p != '=' && *p != ' ' && *p != '\t';); + + if (*p) { + /* + * If there's an equals sign, set the value pointer, and + * skip over the value part of the token. Terminate the + * token. + */ + if (*p == '=') { + *p = '\0'; + for (*valuep = ++p; + *p && *p != ',' && *p != ' ' && *p != '\t'; ++p); + if (*p) + *p++ = '\0'; + } else + *p++ = '\0'; + /* Skip any whitespace or commas after this token. */ + for (; *p && (*p == ',' || *p == ' ' || *p == '\t'); ++p); + } + + /* set optionp for next round. */ + *optionp = p; + + for (cnt = 0; *tokens; ++tokens, ++cnt) + if (!strcmp(suboptarg, *tokens)) + return(cnt); + return(-1); +} + +#endif diff --git a/compat_strlcat.c b/compat_strlcat.c new file mode 100644 index 000000000000..543d40b38b05 --- /dev/null +++ b/compat_strlcat.c @@ -0,0 +1,67 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_STRLCAT + +int dummy; + +#else + +/* $OpenBSD: strlcat.c,v 1.13 2005/08/08 08:05:37 espie Exp $ */ + +/* + * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <string.h> + +/* + * Appends src to string dst of size siz (unlike strncat, siz is the + * full size of dst, not space left). At most siz-1 characters + * will be copied. Always NUL terminates (unless siz <= strlen(dst)). + * Returns strlen(src) + MIN(siz, strlen(initial dst)). + * If retval >= siz, truncation occurred. + */ +size_t +strlcat(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + size_t dlen; + + /* Find the end of dst and adjust bytes left but don't go past end */ + while (n-- != 0 && *d != '\0') + d++; + dlen = d - dst; + n = siz - dlen; + + if (n == 0) + return(dlen + strlen(s)); + while (*s != '\0') { + if (n != 1) { + *d++ = *s; + n--; + } + s++; + } + *d = '\0'; + + return(dlen + (s - src)); /* count does not include NUL */ +} + +#endif diff --git a/compat_strlcpy.c b/compat_strlcpy.c new file mode 100644 index 000000000000..a7c64ff9997c --- /dev/null +++ b/compat_strlcpy.c @@ -0,0 +1,63 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_STRLCPY + +int dummy; + +#else + +/* $OpenBSD: strlcpy.c,v 1.11 2006/05/05 15:27:38 millert Exp $ */ + +/* + * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <string.h> + +/* + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ +size_t +strlcpy(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + + /* Copy as many bytes as will fit */ + if (n != 0) { + while (--n != 0) { + if ((*d++ = *s++) == '\0') + break; + } + } + + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++) + ; + } + + return(s - src - 1); /* count does not include NUL */ +} + +#endif diff --git a/config.h.post b/config.h.post new file mode 100644 index 000000000000..39da2b2f0699 --- /dev/null +++ b/config.h.post @@ -0,0 +1,42 @@ +#include <sys/types.h> + +#if !defined(__BEGIN_DECLS) +# ifdef __cplusplus +# define __BEGIN_DECLS extern "C" { +# else +# define __BEGIN_DECLS +# endif +#endif +#if !defined(__END_DECLS) +# ifdef __cplusplus +# define __END_DECLS } +# else +# define __END_DECLS +# endif +#endif + +#if defined(__APPLE__) +# define htobe32(x) OSSwapHostToBigInt32(x) +# define betoh32(x) OSSwapBigToHostInt32(x) +# define htobe64(x) OSSwapHostToBigInt64(x) +# define betoh64(x) OSSwapBigToHostInt64(x) +#elif defined(__linux__) +# define betoh32(x) be32toh(x) +# define betoh64(x) be64toh(x) +#endif + +#ifndef HAVE_STRLCAT +extern size_t strlcat(char *, const char *, size_t); +#endif +#ifndef HAVE_STRLCPY +extern size_t strlcpy(char *, const char *, size_t); +#endif +#ifndef HAVE_GETSUBOPT +extern int getsubopt(char **, char * const *, char **); +extern char *suboptarg; +#endif +#ifndef HAVE_FGETLN +extern char *fgetln(FILE *, size_t *); +#endif + +#endif /* MANDOC_CONFIG_H */ diff --git a/config.h.pre b/config.h.pre new file mode 100644 index 000000000000..bc594784856c --- /dev/null +++ b/config.h.pre @@ -0,0 +1,8 @@ +#ifndef MANDOC_CONFIG_H +#define MANDOC_CONFIG_H + +#if defined(__linux__) || defined(__MINT__) +# define _GNU_SOURCE /* strptime(), getsubopt() */ +#endif + +#include <stdio.h> diff --git a/demandoc.1 b/demandoc.1 new file mode 100644 index 000000000000..845b9c14b55b --- /dev/null +++ b/demandoc.1 @@ -0,0 +1,109 @@ +.\" $Id: demandoc.1,v 1.6 2011/12/25 19:35:44 kristaps Exp $ +.\" +.\" Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: December 25 2011 $ +.Dt DEMANDOC 1 +.Os +.Sh NAME +.Nm demandoc +.Nd emit only text of UNIX manuals +.Sh SYNOPSIS +.Nm demandoc +.Op Fl w +.Op Ar +.Sh DESCRIPTION +The +.Nm +utility emits only the text portions of well-formed +.Xr mdoc 7 +and +.Xr man 7 +.Ux +manual files. +.Pp +By default, +.Nm +parses standard input and outputs only text nodes, preserving line +and column position. +Escape sequences are omitted from the output. +.Pp +Its arguments are as follows: +.Bl -tag -width Ds +.It Fl w +Output a word list. +This outputs each word of text on its own line. +A +.Qq word , +in this case, refers to whitespace-delimited terms beginning with at +least two letters and not consisting of any escape sequences. +Words have their leading and trailing punctuation +.Pq double-quotes, sentence punctuation, etc. +stripped. +.It Ar +The input files. +.El +.Pp +If a document is not well-formed, it is skipped. +.Pp +The +.Fl i , +.Fl k , +.Fl m , +and +.Fl p +flags are silently discarded for calling compatibility with the +historical deroff. +.Sh EXIT STATUS +The +.Nm +utility exits with one of the following values: +.Pp +.Bl -tag -width Ds -compact +.It 0 +No errors occurred. +.It 6 +An operating system error occurred, for example memory exhaustion or an +error accessing input files. +Such errors cause +.Nm +to exit at once, possibly in the middle of parsing or formatting a file. +The output databases are corrupt and should be removed . +.El +.Sh EXAMPLES +The traditional usage of +.Nm +is for spell-checking manuals on +.Bx . +This is accomplished as follows (assuming British spelling): +.Pp +.Dl $ demandoc -w file.1 | spell -b +.Sh SEE ALSO +.Xr mandoc 1 , +.Xr man 7 +.Xr mdoc 7 +.Sh HISTORY +.Nm +replaces the historical deroff utility for handling modern +.Xr man 7 +and +.Xr mdoc 7 +documents. +.Sh AUTHORS +The +.Nm +utility was written by +.An Kristaps Dzonsons , +.Mt kristaps@bsd.lv . diff --git a/demandoc.c b/demandoc.c new file mode 100644 index 000000000000..2474a358e233 --- /dev/null +++ b/demandoc.c @@ -0,0 +1,257 @@ +/* $Id: demandoc.c,v 1.6 2011/09/01 22:25:53 kristaps Exp $ */ +/* + * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <assert.h> +#include <ctype.h> +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "man.h" +#include "mdoc.h" +#include "mandoc.h" + +static void pline(int, int *, int *, int); +static void pman(const struct man_node *, int *, int *, int); +static void pmandoc(struct mparse *, int, const char *, int); +static void pmdoc(const struct mdoc_node *, int *, int *, int); +static void pstring(const char *, int, int *, int); +static void usage(void); + +static const char *progname; + +int +main(int argc, char *argv[]) +{ + struct mparse *mp; + int ch, i, list; + extern int optind; + + progname = strrchr(argv[0], '/'); + if (progname == NULL) + progname = argv[0]; + else + ++progname; + + mp = NULL; + list = 0; + + while (-1 != (ch = getopt(argc, argv, "ikm:pw"))) + switch (ch) { + case ('i'): + /* FALLTHROUGH */ + case ('k'): + /* FALLTHROUGH */ + case ('m'): + /* FALLTHROUGH */ + case ('p'): + break; + case ('w'): + list = 1; + break; + default: + usage(); + return((int)MANDOCLEVEL_BADARG); + } + + argc -= optind; + argv += optind; + + mp = mparse_alloc(MPARSE_AUTO, MANDOCLEVEL_FATAL, NULL, NULL); + assert(mp); + + if (0 == argc) + pmandoc(mp, STDIN_FILENO, "<stdin>", list); + + for (i = 0; i < argc; i++) { + mparse_reset(mp); + pmandoc(mp, -1, argv[i], list); + } + + mparse_free(mp); + return((int)MANDOCLEVEL_OK); +} + +static void +usage(void) +{ + + fprintf(stderr, "usage: %s [-w] [files...]\n", progname); +} + +static void +pmandoc(struct mparse *mp, int fd, const char *fn, int list) +{ + struct mdoc *mdoc; + struct man *man; + int line, col; + + if (mparse_readfd(mp, fd, fn) >= MANDOCLEVEL_FATAL) { + fprintf(stderr, "%s: Parse failure\n", fn); + return; + } + + mparse_result(mp, &mdoc, &man); + line = 1; + col = 0; + + if (mdoc) + pmdoc(mdoc_node(mdoc), &line, &col, list); + else if (man) + pman(man_node(man), &line, &col, list); + else + return; + + if ( ! list) + putchar('\n'); +} + +/* + * Strip the escapes out of a string, emitting the results. + */ +static void +pstring(const char *p, int col, int *colp, int list) +{ + enum mandoc_esc esc; + const char *start, *end; + int emit; + + /* + * Print as many column spaces til we achieve parity with the + * input document. + */ + +again: + if (list && '\0' != *p) { + while (isspace((unsigned char)*p)) + p++; + + while ('\'' == *p || '(' == *p || '"' == *p) + p++; + + emit = isalpha((unsigned char)p[0]) && + isalpha((unsigned char)p[1]); + + for (start = p; '\0' != *p; p++) + if ('\\' == *p) { + p++; + esc = mandoc_escape(&p, NULL, NULL); + if (ESCAPE_ERROR == esc) + return; + emit = 0; + } else if (isspace((unsigned char)*p)) + break; + + end = p - 1; + + while (end > start) + if ('.' == *end || ',' == *end || + '\'' == *end || '"' == *end || + ')' == *end || '!' == *end || + '?' == *end || ':' == *end || + ';' == *end) + end--; + else + break; + + if (emit && end - start >= 1) { + for ( ; start <= end; start++) + if (ASCII_HYPH == *start) + putchar('-'); + else + putchar((unsigned char)*start); + putchar('\n'); + } + + if (isspace((unsigned char)*p)) + goto again; + + return; + } + + while (*colp < col) { + putchar(' '); + (*colp)++; + } + + /* + * Print the input word, skipping any special characters. + */ + while ('\0' != *p) + if ('\\' == *p) { + p++; + esc = mandoc_escape(&p, NULL, NULL); + if (ESCAPE_ERROR == esc) + break; + } else { + putchar((unsigned char )*p++); + (*colp)++; + } +} + +static void +pline(int line, int *linep, int *col, int list) +{ + + if (list) + return; + + /* + * Print out as many lines as needed to reach parity with the + * original input. + */ + + while (*linep < line) { + putchar('\n'); + (*linep)++; + } + + *col = 0; +} + +static void +pmdoc(const struct mdoc_node *p, int *line, int *col, int list) +{ + + for ( ; p; p = p->next) { + if (MDOC_LINE & p->flags) + pline(p->line, line, col, list); + if (MDOC_TEXT == p->type) + pstring(p->string, p->pos, col, list); + if (p->child) + pmdoc(p->child, line, col, list); + } +} + +static void +pman(const struct man_node *p, int *line, int *col, int list) +{ + + for ( ; p; p = p->next) { + if (MAN_LINE & p->flags) + pline(p->line, line, col, list); + if (MAN_TEXT == p->type) + pstring(p->string, p->pos, col, list); + if (p->child) + pman(p->child, line, col, list); + } +} diff --git a/eqn.7 b/eqn.7 new file mode 100644 index 000000000000..f86b9c496bb0 --- /dev/null +++ b/eqn.7 @@ -0,0 +1,280 @@ +.\" $Id: eqn.7,v 1.28 2011/09/25 18:37:09 schwarze Exp $ +.\" +.\" Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: September 25 2011 $ +.Dt EQN 7 +.Os +.Sh NAME +.Nm eqn +.Nd eqn language reference for mandoc +.Sh DESCRIPTION +The +.Nm eqn +language is an equation-formatting language. +It is used within +.Xr mdoc 7 +and +.Xr man 7 +.Ux +manual pages. +It describes the +.Em structure +of an equation, not its mathematical meaning. +This manual describes the +.Nm +language accepted by the +.Xr mandoc 1 +utility, which corresponds to the Second Edition eqn specification (see +.Sx SEE ALSO +for references). +.Pp +Equations within +.Xr mdoc 7 +or +.Xr man 7 +documents are enclosed by the standalone +.Sq \&.EQ +and +.Sq \&.EN +tags. +Equations are multi-line blocks consisting of formulas and control +statements. +.Sh EQUATION STRUCTURE +Each equation is bracketed by +.Sq \&.EQ +and +.Sq \&.EN +strings. +.Em Note : +these are not the same as +.Xr roff 7 +macros, and may only be invoked as +.Sq \&.EQ . +.Pp +The equation grammar is as follows, where quoted strings are +case-sensitive literals in the input: +.Bd -literal -offset indent +eqn : box | eqn box +box : text + | \*q{\*q eqn \*q}\*q + | \*qdefine\*q text text + | \*qndefine\*q text text + | \*qtdefine\*q text text + | \*qgfont\*q text + | \*qgsize\*q text + | \*qset\*q text text + | \*qundef\*q text + | box pos box + | box mark + | \*qmatrix\*q \*q{\*q [col \*q{\*q list \*q}\*q ]* + | pile \*q{\*q list \*q}\*q + | font box + | \*qsize\*q text box + | \*qleft\*q text eqn [\*qright\*q text] +col : \*qlcol\*q | \*qrcol\*q | \*qccol\*q | \*qcol\*q +text : [^space\e\*q]+ | \e\*q.*\e\*q +pile : \*qlpile\*q | \*qcpile\*q | \*qrpile\*q | \*qpile\*q +pos : \*qover\*q | \*qsup\*q | \*qsub\*q | \*qto\*q | \*qfrom\*q +mark : \*qdot\*q | \*qdotdot\*q | \*qhat\*q | \*qtilde\*q | \*qvec\*q + | \*qdyad\*q | \*qbar\*q | \*qunder\*q +font : \*qroman\*q | \*qitalic\*q | \*qbold\*q | \*qfat\*q +list : eqn + | list \*qabove\*q eqn +space : [\e^~ \et] +.Ed +.Pp +White-space consists of the space, tab, circumflex, and tilde +characters. +If within a quoted string, these space characters are retained. +Quoted strings are also not scanned for replacement definitions. +.Pp +The following text terms are translated into a rendered glyph, if +available: alpha, beta, chi, delta, epsilon, eta, gamma, iota, kappa, +lambda, mu, nu, omega, omicron, phi, pi, psi, rho, sigma, tau, theta, +upsilon, xi, zeta, DELTA, GAMMA, LAMBDA, OMEGA, PHI, PI, PSI, SIGMA, +THETA, UPSILON, XI, inter (intersection), union (union), prod (product), +int (integral), sum (summation), grad (gradient), del (vector +differential), times (multiply), cdot (centre-dot), nothing (zero-width +space), approx (approximately equals), prime (prime), half (one-half), +partial (partial differential), inf (infinity), >> (much greater), << +(much less), \-> (left arrow), <\- (right arrow), += (plus-minus), != +(not equal), == (equivalence), <= (less-than-equal), and >= +(more-than-equal). +.Pp +The following control statements are available: +.Bl -tag -width Ds +.It Cm define +Replace all occurrences of a key with a value. +Its syntax is as follows: +.Pp +.D1 define Ar key cvalc +.Pp +The first character of the value string, +.Ar c , +is used as the delimiter for the value +.Ar val . +This allows for arbitrary enclosure of terms (not just quotes), such as +.Pp +.D1 define Ar foo 'bar baz' +.D1 define Ar foo cbar bazc +.Pp +It is an error to have an empty +.Ar key +or +.Ar val . +Note that a quoted +.Ar key +causes errors in some +.Nm +implementations and should not be considered portable. +It is not expanded for replacements. +Definitions may refer to other definitions; these are evaluated +recursively when text replacement occurs and not when the definition is +created. +.Pp +Definitions can create arbitrary strings, for example, the following is +a legal construction. +.Bd -literal -offset indent +define foo 'define' +foo bar 'baz' +.Ed +.Pp +Self-referencing definitions will raise an error. +The +.Cm ndefine +statement is a synonym for +.Cm define , +while +.Cm tdefine +is discarded. +.It Cm gfont +Set the default font of subsequent output. +Its syntax is as follows: +.Pp +.D1 gfont Ar font +.Pp +In mandoc, this value is discarded. +.It Cm gsize +Set the default size of subsequent output. +Its syntax is as follows: +.Pp +.D1 gsize Ar size +.Pp +The +.Ar size +value should be an integer. +.It Cm set +Set an equation mode. +In mandoc, both arguments are thrown away. +Its syntax is as follows: +.Pp +.D1 set Ar key val +.Pp +The +.Ar key +and +.Ar val +are not expanded for replacements. +This statement is a GNU extension. +.It Cm undef +Unset a previously-defined key. +Its syntax is as follows: +.Pp +.D1 define Ar key +.Pp +Once invoked, the definition for +.Ar key +is discarded. +The +.Ar key +is not expanded for replacements. +This statement is a GNU extension. +.El +.Sh COMPATIBILITY +This section documents the compatibility of mandoc +.Nm +and the troff +.Nm +implementation (including GNU troff). +.Pp +.Bl -dash -compact +.It +The text string +.Sq \e\*q +is interpreted as a literal quote in troff. +In mandoc, this is interpreted as a comment. +.It +In troff, The circumflex and tilde white-space symbols map to +fixed-width spaces. +In mandoc, these characters are synonyms for the space character. +.It +The troff implementation of +.Nm +allows for equation alignment with the +.Cm mark +and +.Cm lineup +tokens. +mandoc discards these tokens. +The +.Cm back Ar n , +.Cm fwd Ar n , +.Cm up Ar n , +and +.Cm down Ar n +commands are also ignored. +.El +.Sh SEE ALSO +.Xr mandoc 1 , +.Xr man 7 , +.Xr mandoc_char 7 , +.Xr mdoc 7 , +.Xr roff 7 +.Rs +.%A Brian W. Kernighan +.%A Lorinda L. Cherry +.%T System for Typesetting Mathematics +.%J Communications of the ACM +.%V 18 +.%P 151\(en157 +.%D March, 1975 +.Re +.Rs +.%A Brian W. Kernighan +.%A Lorinda L. Cherry +.%T Typesetting Mathematics, User's Guide +.%D 1976 +.Re +.Rs +.%A Brian W. Kernighan +.%A Lorinda L. Cherry +.%T Typesetting Mathematics, User's Guide (Second Edition) +.%D 1978 +.Re +.Sh HISTORY +The eqn utility, a preprocessor for troff, was originally written by +Brian W. Kernighan and Lorinda L. Cherry in 1975. +The GNU reimplementation of eqn, part of the GNU troff package, was +released in 1989 by James Clark. +The eqn component of +.Xr mandoc 1 +was added in 2011. +.Sh AUTHORS +This +.Nm +reference was written by +.An Kristaps Dzonsons , +.Mt kristaps@bsd.lv . diff --git a/eqn.c b/eqn.c new file mode 100644 index 000000000000..37f01bcb5b6e --- /dev/null +++ b/eqn.c @@ -0,0 +1,949 @@ +/* $Id: eqn.c,v 1.38 2011/07/25 15:37:00 kristaps Exp $ */ +/* + * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <assert.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "mandoc.h" +#include "libmandoc.h" +#include "libroff.h" + +#define EQN_NEST_MAX 128 /* maximum nesting of defines */ +#define EQN_MSG(t, x) mandoc_msg((t), (x)->parse, (x)->eqn.ln, (x)->eqn.pos, NULL) + +enum eqn_rest { + EQN_DESCOPE, + EQN_ERR, + EQN_OK, + EQN_EOF +}; + +enum eqn_symt { + EQNSYM_alpha, + EQNSYM_beta, + EQNSYM_chi, + EQNSYM_delta, + EQNSYM_epsilon, + EQNSYM_eta, + EQNSYM_gamma, + EQNSYM_iota, + EQNSYM_kappa, + EQNSYM_lambda, + EQNSYM_mu, + EQNSYM_nu, + EQNSYM_omega, + EQNSYM_omicron, + EQNSYM_phi, + EQNSYM_pi, + EQNSYM_ps, + EQNSYM_rho, + EQNSYM_sigma, + EQNSYM_tau, + EQNSYM_theta, + EQNSYM_upsilon, + EQNSYM_xi, + EQNSYM_zeta, + EQNSYM_DELTA, + EQNSYM_GAMMA, + EQNSYM_LAMBDA, + EQNSYM_OMEGA, + EQNSYM_PHI, + EQNSYM_PI, + EQNSYM_PSI, + EQNSYM_SIGMA, + EQNSYM_THETA, + EQNSYM_UPSILON, + EQNSYM_XI, + EQNSYM_inter, + EQNSYM_union, + EQNSYM_prod, + EQNSYM_int, + EQNSYM_sum, + EQNSYM_grad, + EQNSYM_del, + EQNSYM_times, + EQNSYM_cdot, + EQNSYM_nothing, + EQNSYM_approx, + EQNSYM_prime, + EQNSYM_half, + EQNSYM_partial, + EQNSYM_inf, + EQNSYM_muchgreat, + EQNSYM_muchless, + EQNSYM_larrow, + EQNSYM_rarrow, + EQNSYM_pm, + EQNSYM_nequal, + EQNSYM_equiv, + EQNSYM_lessequal, + EQNSYM_moreequal, + EQNSYM__MAX +}; + +enum eqnpartt { + EQN_DEFINE = 0, + EQN_NDEFINE, + EQN_TDEFINE, + EQN_SET, + EQN_UNDEF, + EQN_GFONT, + EQN_GSIZE, + EQN_BACK, + EQN_FWD, + EQN_UP, + EQN_DOWN, + EQN__MAX +}; + +struct eqnstr { + const char *name; + size_t sz; +}; + +#define STRNEQ(p1, sz1, p2, sz2) \ + ((sz1) == (sz2) && 0 == strncmp((p1), (p2), (sz1))) +#define EQNSTREQ(x, p, sz) \ + STRNEQ((x)->name, (x)->sz, (p), (sz)) + +struct eqnpart { + struct eqnstr str; + int (*fp)(struct eqn_node *); +}; + +struct eqnsym { + struct eqnstr str; + const char *sym; +}; + + +static enum eqn_rest eqn_box(struct eqn_node *, struct eqn_box *); +static struct eqn_box *eqn_box_alloc(struct eqn_node *, + struct eqn_box *); +static void eqn_box_free(struct eqn_box *); +static struct eqn_def *eqn_def_find(struct eqn_node *, + const char *, size_t); +static int eqn_do_gfont(struct eqn_node *); +static int eqn_do_gsize(struct eqn_node *); +static int eqn_do_define(struct eqn_node *); +static int eqn_do_ign1(struct eqn_node *); +static int eqn_do_ign2(struct eqn_node *); +static int eqn_do_tdefine(struct eqn_node *); +static int eqn_do_undef(struct eqn_node *); +static enum eqn_rest eqn_eqn(struct eqn_node *, struct eqn_box *); +static enum eqn_rest eqn_list(struct eqn_node *, struct eqn_box *); +static enum eqn_rest eqn_matrix(struct eqn_node *, struct eqn_box *); +static const char *eqn_nexttok(struct eqn_node *, size_t *); +static const char *eqn_nextrawtok(struct eqn_node *, size_t *); +static const char *eqn_next(struct eqn_node *, + char, size_t *, int); +static void eqn_rewind(struct eqn_node *); + +static const struct eqnpart eqnparts[EQN__MAX] = { + { { "define", 6 }, eqn_do_define }, /* EQN_DEFINE */ + { { "ndefine", 7 }, eqn_do_define }, /* EQN_NDEFINE */ + { { "tdefine", 7 }, eqn_do_tdefine }, /* EQN_TDEFINE */ + { { "set", 3 }, eqn_do_ign2 }, /* EQN_SET */ + { { "undef", 5 }, eqn_do_undef }, /* EQN_UNDEF */ + { { "gfont", 5 }, eqn_do_gfont }, /* EQN_GFONT */ + { { "gsize", 5 }, eqn_do_gsize }, /* EQN_GSIZE */ + { { "back", 4 }, eqn_do_ign1 }, /* EQN_BACK */ + { { "fwd", 3 }, eqn_do_ign1 }, /* EQN_FWD */ + { { "up", 2 }, eqn_do_ign1 }, /* EQN_UP */ + { { "down", 4 }, eqn_do_ign1 }, /* EQN_DOWN */ +}; + +static const struct eqnstr eqnmarks[EQNMARK__MAX] = { + { "", 0 }, /* EQNMARK_NONE */ + { "dot", 3 }, /* EQNMARK_DOT */ + { "dotdot", 6 }, /* EQNMARK_DOTDOT */ + { "hat", 3 }, /* EQNMARK_HAT */ + { "tilde", 5 }, /* EQNMARK_TILDE */ + { "vec", 3 }, /* EQNMARK_VEC */ + { "dyad", 4 }, /* EQNMARK_DYAD */ + { "bar", 3 }, /* EQNMARK_BAR */ + { "under", 5 }, /* EQNMARK_UNDER */ +}; + +static const struct eqnstr eqnfonts[EQNFONT__MAX] = { + { "", 0 }, /* EQNFONT_NONE */ + { "roman", 5 }, /* EQNFONT_ROMAN */ + { "bold", 4 }, /* EQNFONT_BOLD */ + { "fat", 3 }, /* EQNFONT_FAT */ + { "italic", 6 }, /* EQNFONT_ITALIC */ +}; + +static const struct eqnstr eqnposs[EQNPOS__MAX] = { + { "", 0 }, /* EQNPOS_NONE */ + { "over", 4 }, /* EQNPOS_OVER */ + { "sup", 3 }, /* EQNPOS_SUP */ + { "sub", 3 }, /* EQNPOS_SUB */ + { "to", 2 }, /* EQNPOS_TO */ + { "from", 4 }, /* EQNPOS_FROM */ +}; + +static const struct eqnstr eqnpiles[EQNPILE__MAX] = { + { "", 0 }, /* EQNPILE_NONE */ + { "pile", 4 }, /* EQNPILE_PILE */ + { "cpile", 5 }, /* EQNPILE_CPILE */ + { "rpile", 5 }, /* EQNPILE_RPILE */ + { "lpile", 5 }, /* EQNPILE_LPILE */ + { "col", 3 }, /* EQNPILE_COL */ + { "ccol", 4 }, /* EQNPILE_CCOL */ + { "rcol", 4 }, /* EQNPILE_RCOL */ + { "lcol", 4 }, /* EQNPILE_LCOL */ +}; + +static const struct eqnsym eqnsyms[EQNSYM__MAX] = { + { { "alpha", 5 }, "*a" }, /* EQNSYM_alpha */ + { { "beta", 4 }, "*b" }, /* EQNSYM_beta */ + { { "chi", 3 }, "*x" }, /* EQNSYM_chi */ + { { "delta", 5 }, "*d" }, /* EQNSYM_delta */ + { { "epsilon", 7 }, "*e" }, /* EQNSYM_epsilon */ + { { "eta", 3 }, "*y" }, /* EQNSYM_eta */ + { { "gamma", 5 }, "*g" }, /* EQNSYM_gamma */ + { { "iota", 4 }, "*i" }, /* EQNSYM_iota */ + { { "kappa", 5 }, "*k" }, /* EQNSYM_kappa */ + { { "lambda", 6 }, "*l" }, /* EQNSYM_lambda */ + { { "mu", 2 }, "*m" }, /* EQNSYM_mu */ + { { "nu", 2 }, "*n" }, /* EQNSYM_nu */ + { { "omega", 5 }, "*w" }, /* EQNSYM_omega */ + { { "omicron", 7 }, "*o" }, /* EQNSYM_omicron */ + { { "phi", 3 }, "*f" }, /* EQNSYM_phi */ + { { "pi", 2 }, "*p" }, /* EQNSYM_pi */ + { { "psi", 2 }, "*q" }, /* EQNSYM_psi */ + { { "rho", 3 }, "*r" }, /* EQNSYM_rho */ + { { "sigma", 5 }, "*s" }, /* EQNSYM_sigma */ + { { "tau", 3 }, "*t" }, /* EQNSYM_tau */ + { { "theta", 5 }, "*h" }, /* EQNSYM_theta */ + { { "upsilon", 7 }, "*u" }, /* EQNSYM_upsilon */ + { { "xi", 2 }, "*c" }, /* EQNSYM_xi */ + { { "zeta", 4 }, "*z" }, /* EQNSYM_zeta */ + { { "DELTA", 5 }, "*D" }, /* EQNSYM_DELTA */ + { { "GAMMA", 5 }, "*G" }, /* EQNSYM_GAMMA */ + { { "LAMBDA", 6 }, "*L" }, /* EQNSYM_LAMBDA */ + { { "OMEGA", 5 }, "*W" }, /* EQNSYM_OMEGA */ + { { "PHI", 3 }, "*F" }, /* EQNSYM_PHI */ + { { "PI", 2 }, "*P" }, /* EQNSYM_PI */ + { { "PSI", 3 }, "*Q" }, /* EQNSYM_PSI */ + { { "SIGMA", 5 }, "*S" }, /* EQNSYM_SIGMA */ + { { "THETA", 5 }, "*H" }, /* EQNSYM_THETA */ + { { "UPSILON", 7 }, "*U" }, /* EQNSYM_UPSILON */ + { { "XI", 2 }, "*C" }, /* EQNSYM_XI */ + { { "inter", 5 }, "ca" }, /* EQNSYM_inter */ + { { "union", 5 }, "cu" }, /* EQNSYM_union */ + { { "prod", 4 }, "product" }, /* EQNSYM_prod */ + { { "int", 3 }, "integral" }, /* EQNSYM_int */ + { { "sum", 3 }, "sum" }, /* EQNSYM_sum */ + { { "grad", 4 }, "gr" }, /* EQNSYM_grad */ + { { "del", 3 }, "gr" }, /* EQNSYM_del */ + { { "times", 5 }, "mu" }, /* EQNSYM_times */ + { { "cdot", 4 }, "pc" }, /* EQNSYM_cdot */ + { { "nothing", 7 }, "&" }, /* EQNSYM_nothing */ + { { "approx", 6 }, "~~" }, /* EQNSYM_approx */ + { { "prime", 5 }, "aq" }, /* EQNSYM_prime */ + { { "half", 4 }, "12" }, /* EQNSYM_half */ + { { "partial", 7 }, "pd" }, /* EQNSYM_partial */ + { { "inf", 3 }, "if" }, /* EQNSYM_inf */ + { { ">>", 2 }, ">>" }, /* EQNSYM_muchgreat */ + { { "<<", 2 }, "<<" }, /* EQNSYM_muchless */ + { { "<-", 2 }, "<-" }, /* EQNSYM_larrow */ + { { "->", 2 }, "->" }, /* EQNSYM_rarrow */ + { { "+-", 2 }, "+-" }, /* EQNSYM_pm */ + { { "!=", 2 }, "!=" }, /* EQNSYM_nequal */ + { { "==", 2 }, "==" }, /* EQNSYM_equiv */ + { { "<=", 2 }, "<=" }, /* EQNSYM_lessequal */ + { { ">=", 2 }, ">=" }, /* EQNSYM_moreequal */ +}; + +/* ARGSUSED */ +enum rofferr +eqn_read(struct eqn_node **epp, int ln, + const char *p, int pos, int *offs) +{ + size_t sz; + struct eqn_node *ep; + enum rofferr er; + + ep = *epp; + + /* + * If we're the terminating mark, unset our equation status and + * validate the full equation. + */ + + if (0 == strncmp(p, ".EN", 3)) { + er = eqn_end(epp); + p += 3; + while (' ' == *p || '\t' == *p) + p++; + if ('\0' == *p) + return(er); + mandoc_msg(MANDOCERR_ARGSLOST, ep->parse, ln, pos, NULL); + return(er); + } + + /* + * Build up the full string, replacing all newlines with regular + * whitespace. + */ + + sz = strlen(p + pos) + 1; + ep->data = mandoc_realloc(ep->data, ep->sz + sz + 1); + + /* First invocation: nil terminate the string. */ + + if (0 == ep->sz) + *ep->data = '\0'; + + ep->sz += sz; + strlcat(ep->data, p + pos, ep->sz + 1); + strlcat(ep->data, " ", ep->sz + 1); + return(ROFF_IGN); +} + +struct eqn_node * +eqn_alloc(const char *name, int pos, int line, struct mparse *parse) +{ + struct eqn_node *p; + size_t sz; + const char *end; + + p = mandoc_calloc(1, sizeof(struct eqn_node)); + + if (name && '\0' != *name) { + sz = strlen(name); + assert(sz); + do { + sz--; + end = name + (int)sz; + } while (' ' == *end || '\t' == *end); + p->eqn.name = mandoc_strndup(name, sz + 1); + } + + p->parse = parse; + p->eqn.ln = line; + p->eqn.pos = pos; + p->gsize = EQN_DEFSIZE; + + return(p); +} + +enum rofferr +eqn_end(struct eqn_node **epp) +{ + struct eqn_node *ep; + struct eqn_box *root; + enum eqn_rest c; + + ep = *epp; + *epp = NULL; + + ep->eqn.root = mandoc_calloc(1, sizeof(struct eqn_box)); + + root = ep->eqn.root; + root->type = EQN_ROOT; + + if (0 == ep->sz) + return(ROFF_IGN); + + if (EQN_DESCOPE == (c = eqn_eqn(ep, root))) { + EQN_MSG(MANDOCERR_EQNNSCOPE, ep); + c = EQN_ERR; + } + + return(EQN_EOF == c ? ROFF_EQN : ROFF_IGN); +} + +static enum eqn_rest +eqn_eqn(struct eqn_node *ep, struct eqn_box *last) +{ + struct eqn_box *bp; + enum eqn_rest c; + + bp = eqn_box_alloc(ep, last); + bp->type = EQN_SUBEXPR; + + while (EQN_OK == (c = eqn_box(ep, bp))) + /* Spin! */ ; + + return(c); +} + +static enum eqn_rest +eqn_matrix(struct eqn_node *ep, struct eqn_box *last) +{ + struct eqn_box *bp; + const char *start; + size_t sz; + enum eqn_rest c; + + bp = eqn_box_alloc(ep, last); + bp->type = EQN_MATRIX; + + if (NULL == (start = eqn_nexttok(ep, &sz))) { + EQN_MSG(MANDOCERR_EQNEOF, ep); + return(EQN_ERR); + } + if ( ! STRNEQ(start, sz, "{", 1)) { + EQN_MSG(MANDOCERR_EQNSYNT, ep); + return(EQN_ERR); + } + + while (EQN_OK == (c = eqn_box(ep, bp))) + switch (bp->last->pile) { + case (EQNPILE_LCOL): + /* FALLTHROUGH */ + case (EQNPILE_CCOL): + /* FALLTHROUGH */ + case (EQNPILE_RCOL): + continue; + default: + EQN_MSG(MANDOCERR_EQNSYNT, ep); + return(EQN_ERR); + }; + + if (EQN_DESCOPE != c) { + if (EQN_EOF == c) + EQN_MSG(MANDOCERR_EQNEOF, ep); + return(EQN_ERR); + } + + eqn_rewind(ep); + start = eqn_nexttok(ep, &sz); + assert(start); + if (STRNEQ(start, sz, "}", 1)) + return(EQN_OK); + + EQN_MSG(MANDOCERR_EQNBADSCOPE, ep); + return(EQN_ERR); +} + +static enum eqn_rest +eqn_list(struct eqn_node *ep, struct eqn_box *last) +{ + struct eqn_box *bp; + const char *start; + size_t sz; + enum eqn_rest c; + + bp = eqn_box_alloc(ep, last); + bp->type = EQN_LIST; + + if (NULL == (start = eqn_nexttok(ep, &sz))) { + EQN_MSG(MANDOCERR_EQNEOF, ep); + return(EQN_ERR); + } + if ( ! STRNEQ(start, sz, "{", 1)) { + EQN_MSG(MANDOCERR_EQNSYNT, ep); + return(EQN_ERR); + } + + while (EQN_DESCOPE == (c = eqn_eqn(ep, bp))) { + eqn_rewind(ep); + start = eqn_nexttok(ep, &sz); + assert(start); + if ( ! STRNEQ(start, sz, "above", 5)) + break; + } + + if (EQN_DESCOPE != c) { + if (EQN_ERR != c) + EQN_MSG(MANDOCERR_EQNSCOPE, ep); + return(EQN_ERR); + } + + eqn_rewind(ep); + start = eqn_nexttok(ep, &sz); + assert(start); + if (STRNEQ(start, sz, "}", 1)) + return(EQN_OK); + + EQN_MSG(MANDOCERR_EQNBADSCOPE, ep); + return(EQN_ERR); +} + +static enum eqn_rest +eqn_box(struct eqn_node *ep, struct eqn_box *last) +{ + size_t sz; + const char *start; + char *left; + char sym[64]; + enum eqn_rest c; + int i, size; + struct eqn_box *bp; + + if (NULL == (start = eqn_nexttok(ep, &sz))) + return(EQN_EOF); + + if (STRNEQ(start, sz, "}", 1)) + return(EQN_DESCOPE); + else if (STRNEQ(start, sz, "right", 5)) + return(EQN_DESCOPE); + else if (STRNEQ(start, sz, "above", 5)) + return(EQN_DESCOPE); + else if (STRNEQ(start, sz, "mark", 4)) + return(EQN_OK); + else if (STRNEQ(start, sz, "lineup", 6)) + return(EQN_OK); + + for (i = 0; i < (int)EQN__MAX; i++) { + if ( ! EQNSTREQ(&eqnparts[i].str, start, sz)) + continue; + return((*eqnparts[i].fp)(ep) ? + EQN_OK : EQN_ERR); + } + + if (STRNEQ(start, sz, "{", 1)) { + if (EQN_DESCOPE != (c = eqn_eqn(ep, last))) { + if (EQN_ERR != c) + EQN_MSG(MANDOCERR_EQNSCOPE, ep); + return(EQN_ERR); + } + eqn_rewind(ep); + start = eqn_nexttok(ep, &sz); + assert(start); + if (STRNEQ(start, sz, "}", 1)) + return(EQN_OK); + EQN_MSG(MANDOCERR_EQNBADSCOPE, ep); + return(EQN_ERR); + } + + for (i = 0; i < (int)EQNPILE__MAX; i++) { + if ( ! EQNSTREQ(&eqnpiles[i], start, sz)) + continue; + if (EQN_OK == (c = eqn_list(ep, last))) + last->last->pile = (enum eqn_pilet)i; + return(c); + } + + if (STRNEQ(start, sz, "matrix", 6)) + return(eqn_matrix(ep, last)); + + if (STRNEQ(start, sz, "left", 4)) { + if (NULL == (start = eqn_nexttok(ep, &sz))) { + EQN_MSG(MANDOCERR_EQNEOF, ep); + return(EQN_ERR); + } + left = mandoc_strndup(start, sz); + c = eqn_eqn(ep, last); + if (last->last) + last->last->left = left; + else + free(left); + if (EQN_DESCOPE != c) + return(c); + assert(last->last); + eqn_rewind(ep); + start = eqn_nexttok(ep, &sz); + assert(start); + if ( ! STRNEQ(start, sz, "right", 5)) + return(EQN_DESCOPE); + if (NULL == (start = eqn_nexttok(ep, &sz))) { + EQN_MSG(MANDOCERR_EQNEOF, ep); + return(EQN_ERR); + } + last->last->right = mandoc_strndup(start, sz); + return(EQN_OK); + } + + for (i = 0; i < (int)EQNPOS__MAX; i++) { + if ( ! EQNSTREQ(&eqnposs[i], start, sz)) + continue; + if (NULL == last->last) { + EQN_MSG(MANDOCERR_EQNSYNT, ep); + return(EQN_ERR); + } + last->last->pos = (enum eqn_post)i; + if (EQN_EOF == (c = eqn_box(ep, last))) { + EQN_MSG(MANDOCERR_EQNEOF, ep); + return(EQN_ERR); + } + return(c); + } + + for (i = 0; i < (int)EQNMARK__MAX; i++) { + if ( ! EQNSTREQ(&eqnmarks[i], start, sz)) + continue; + if (NULL == last->last) { + EQN_MSG(MANDOCERR_EQNSYNT, ep); + return(EQN_ERR); + } + last->last->mark = (enum eqn_markt)i; + if (EQN_EOF == (c = eqn_box(ep, last))) { + EQN_MSG(MANDOCERR_EQNEOF, ep); + return(EQN_ERR); + } + return(c); + } + + for (i = 0; i < (int)EQNFONT__MAX; i++) { + if ( ! EQNSTREQ(&eqnfonts[i], start, sz)) + continue; + if (EQN_EOF == (c = eqn_box(ep, last))) { + EQN_MSG(MANDOCERR_EQNEOF, ep); + return(EQN_ERR); + } else if (EQN_OK == c) + last->last->font = (enum eqn_fontt)i; + return(c); + } + + if (STRNEQ(start, sz, "size", 4)) { + if (NULL == (start = eqn_nexttok(ep, &sz))) { + EQN_MSG(MANDOCERR_EQNEOF, ep); + return(EQN_ERR); + } + size = mandoc_strntoi(start, sz, 10); + if (EQN_EOF == (c = eqn_box(ep, last))) { + EQN_MSG(MANDOCERR_EQNEOF, ep); + return(EQN_ERR); + } else if (EQN_OK != c) + return(c); + last->last->size = size; + } + + bp = eqn_box_alloc(ep, last); + bp->type = EQN_TEXT; + for (i = 0; i < (int)EQNSYM__MAX; i++) + if (EQNSTREQ(&eqnsyms[i].str, start, sz)) { + sym[63] = '\0'; + snprintf(sym, 62, "\\[%s]", eqnsyms[i].sym); + bp->text = mandoc_strdup(sym); + return(EQN_OK); + } + + bp->text = mandoc_strndup(start, sz); + return(EQN_OK); +} + +void +eqn_free(struct eqn_node *p) +{ + int i; + + eqn_box_free(p->eqn.root); + + for (i = 0; i < (int)p->defsz; i++) { + free(p->defs[i].key); + free(p->defs[i].val); + } + + free(p->eqn.name); + free(p->data); + free(p->defs); + free(p); +} + +static struct eqn_box * +eqn_box_alloc(struct eqn_node *ep, struct eqn_box *parent) +{ + struct eqn_box *bp; + + bp = mandoc_calloc(1, sizeof(struct eqn_box)); + bp->parent = parent; + bp->size = ep->gsize; + + if (NULL == parent->first) + parent->first = bp; + else + parent->last->next = bp; + + parent->last = bp; + return(bp); +} + +static void +eqn_box_free(struct eqn_box *bp) +{ + + if (bp->first) + eqn_box_free(bp->first); + if (bp->next) + eqn_box_free(bp->next); + + free(bp->text); + free(bp->left); + free(bp->right); + free(bp); +} + +static const char * +eqn_nextrawtok(struct eqn_node *ep, size_t *sz) +{ + + return(eqn_next(ep, '"', sz, 0)); +} + +static const char * +eqn_nexttok(struct eqn_node *ep, size_t *sz) +{ + + return(eqn_next(ep, '"', sz, 1)); +} + +static void +eqn_rewind(struct eqn_node *ep) +{ + + ep->cur = ep->rew; +} + +static const char * +eqn_next(struct eqn_node *ep, char quote, size_t *sz, int repl) +{ + char *start, *next; + int q, diff, lim; + size_t ssz, dummy; + struct eqn_def *def; + + if (NULL == sz) + sz = &dummy; + + lim = 0; + ep->rew = ep->cur; +again: + /* Prevent self-definitions. */ + + if (lim >= EQN_NEST_MAX) { + EQN_MSG(MANDOCERR_ROFFLOOP, ep); + return(NULL); + } + + ep->cur = ep->rew; + start = &ep->data[(int)ep->cur]; + q = 0; + + if ('\0' == *start) + return(NULL); + + if (quote == *start) { + ep->cur++; + q = 1; + } + + start = &ep->data[(int)ep->cur]; + + if ( ! q) { + if ('{' == *start || '}' == *start) + ssz = 1; + else + ssz = strcspn(start + 1, " ^~\"{}\t") + 1; + next = start + (int)ssz; + if ('\0' == *next) + next = NULL; + } else + next = strchr(start, quote); + + if (NULL != next) { + *sz = (size_t)(next - start); + ep->cur += *sz; + if (q) + ep->cur++; + while (' ' == ep->data[(int)ep->cur] || + '\t' == ep->data[(int)ep->cur] || + '^' == ep->data[(int)ep->cur] || + '~' == ep->data[(int)ep->cur]) + ep->cur++; + } else { + if (q) + EQN_MSG(MANDOCERR_BADQUOTE, ep); + next = strchr(start, '\0'); + *sz = (size_t)(next - start); + ep->cur += *sz; + } + + /* Quotes aren't expanded for values. */ + + if (q || ! repl) + return(start); + + if (NULL != (def = eqn_def_find(ep, start, *sz))) { + diff = def->valsz - *sz; + + if (def->valsz > *sz) { + ep->sz += diff; + ep->data = mandoc_realloc(ep->data, ep->sz + 1); + ep->data[ep->sz] = '\0'; + start = &ep->data[(int)ep->rew]; + } + + diff = def->valsz - *sz; + memmove(start + *sz + diff, start + *sz, + (strlen(start) - *sz) + 1); + memcpy(start, def->val, def->valsz); + goto again; + } + + return(start); +} + +static int +eqn_do_ign1(struct eqn_node *ep) +{ + + if (NULL == eqn_nextrawtok(ep, NULL)) + EQN_MSG(MANDOCERR_EQNEOF, ep); + else + return(1); + + return(0); +} + +static int +eqn_do_ign2(struct eqn_node *ep) +{ + + if (NULL == eqn_nextrawtok(ep, NULL)) + EQN_MSG(MANDOCERR_EQNEOF, ep); + else if (NULL == eqn_nextrawtok(ep, NULL)) + EQN_MSG(MANDOCERR_EQNEOF, ep); + else + return(1); + + return(0); +} + +static int +eqn_do_tdefine(struct eqn_node *ep) +{ + + if (NULL == eqn_nextrawtok(ep, NULL)) + EQN_MSG(MANDOCERR_EQNEOF, ep); + else if (NULL == eqn_next(ep, ep->data[(int)ep->cur], NULL, 0)) + EQN_MSG(MANDOCERR_EQNEOF, ep); + else + return(1); + + return(0); +} + +static int +eqn_do_define(struct eqn_node *ep) +{ + const char *start; + size_t sz; + struct eqn_def *def; + int i; + + if (NULL == (start = eqn_nextrawtok(ep, &sz))) { + EQN_MSG(MANDOCERR_EQNEOF, ep); + return(0); + } + + /* + * Search for a key that already exists. + * Create a new key if none is found. + */ + + if (NULL == (def = eqn_def_find(ep, start, sz))) { + /* Find holes in string array. */ + for (i = 0; i < (int)ep->defsz; i++) + if (0 == ep->defs[i].keysz) + break; + + if (i == (int)ep->defsz) { + ep->defsz++; + ep->defs = mandoc_realloc + (ep->defs, ep->defsz * + sizeof(struct eqn_def)); + ep->defs[i].key = ep->defs[i].val = NULL; + } + + ep->defs[i].keysz = sz; + ep->defs[i].key = mandoc_realloc + (ep->defs[i].key, sz + 1); + + memcpy(ep->defs[i].key, start, sz); + ep->defs[i].key[(int)sz] = '\0'; + def = &ep->defs[i]; + } + + start = eqn_next(ep, ep->data[(int)ep->cur], &sz, 0); + + if (NULL == start) { + EQN_MSG(MANDOCERR_EQNEOF, ep); + return(0); + } + + def->valsz = sz; + def->val = mandoc_realloc(def->val, sz + 1); + memcpy(def->val, start, sz); + def->val[(int)sz] = '\0'; + return(1); +} + +static int +eqn_do_gfont(struct eqn_node *ep) +{ + + if (NULL == eqn_nextrawtok(ep, NULL)) { + EQN_MSG(MANDOCERR_EQNEOF, ep); + return(0); + } + return(1); +} + +static int +eqn_do_gsize(struct eqn_node *ep) +{ + const char *start; + size_t sz; + + if (NULL == (start = eqn_nextrawtok(ep, &sz))) { + EQN_MSG(MANDOCERR_EQNEOF, ep); + return(0); + } + ep->gsize = mandoc_strntoi(start, sz, 10); + return(1); +} + +static int +eqn_do_undef(struct eqn_node *ep) +{ + const char *start; + struct eqn_def *def; + size_t sz; + + if (NULL == (start = eqn_nextrawtok(ep, &sz))) { + EQN_MSG(MANDOCERR_EQNEOF, ep); + return(0); + } else if (NULL != (def = eqn_def_find(ep, start, sz))) + def->keysz = 0; + + return(1); +} + +static struct eqn_def * +eqn_def_find(struct eqn_node *ep, const char *key, size_t sz) +{ + int i; + + for (i = 0; i < (int)ep->defsz; i++) + if (ep->defs[i].keysz && STRNEQ(ep->defs[i].key, + ep->defs[i].keysz, key, sz)) + return(&ep->defs[i]); + + return(NULL); +} diff --git a/eqn_html.c b/eqn_html.c new file mode 100644 index 000000000000..80c82f1de5b5 --- /dev/null +++ b/eqn_html.c @@ -0,0 +1,81 @@ +/* $Id: eqn_html.c,v 1.2 2011/07/24 10:09:03 kristaps Exp $ */ +/* + * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "mandoc.h" +#include "out.h" +#include "html.h" + +static const enum htmltag fontmap[EQNFONT__MAX] = { + TAG_SPAN, /* EQNFONT_NONE */ + TAG_SPAN, /* EQNFONT_ROMAN */ + TAG_B, /* EQNFONT_BOLD */ + TAG_B, /* EQNFONT_FAT */ + TAG_I /* EQNFONT_ITALIC */ +}; + + +static void eqn_box(struct html *, const struct eqn_box *); + +void +print_eqn(struct html *p, const struct eqn *ep) +{ + struct htmlpair tag; + struct tag *t; + + PAIR_CLASS_INIT(&tag, "eqn"); + t = print_otag(p, TAG_SPAN, 1, &tag); + + p->flags |= HTML_NONOSPACE; + eqn_box(p, ep->root); + p->flags &= ~HTML_NONOSPACE; + + print_tagq(p, t); +} + +static void +eqn_box(struct html *p, const struct eqn_box *bp) +{ + struct tag *t; + + t = EQNFONT_NONE == bp->font ? NULL : + print_otag(p, fontmap[(int)bp->font], 0, NULL); + + if (bp->left) + print_text(p, bp->left); + + if (bp->text) + print_text(p, bp->text); + + if (bp->first) + eqn_box(p, bp->first); + + if (NULL != t) + print_tagq(p, t); + if (bp->right) + print_text(p, bp->right); + + if (bp->next) + eqn_box(p, bp->next); +} diff --git a/eqn_term.c b/eqn_term.c new file mode 100644 index 000000000000..cfbd8d48f807 --- /dev/null +++ b/eqn_term.c @@ -0,0 +1,76 @@ +/* $Id: eqn_term.c,v 1.4 2011/07/24 10:09:03 kristaps Exp $ */ +/* + * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "mandoc.h" +#include "out.h" +#include "term.h" + +static const enum termfont fontmap[EQNFONT__MAX] = { + TERMFONT_NONE, /* EQNFONT_NONE */ + TERMFONT_NONE, /* EQNFONT_ROMAN */ + TERMFONT_BOLD, /* EQNFONT_BOLD */ + TERMFONT_BOLD, /* EQNFONT_FAT */ + TERMFONT_UNDER /* EQNFONT_ITALIC */ +}; + +static void eqn_box(struct termp *, const struct eqn_box *); + +void +term_eqn(struct termp *p, const struct eqn *ep) +{ + + p->flags |= TERMP_NONOSPACE; + eqn_box(p, ep->root); + term_word(p, " "); + p->flags &= ~TERMP_NONOSPACE; +} + +static void +eqn_box(struct termp *p, const struct eqn_box *bp) +{ + + if (EQNFONT_NONE != bp->font) + term_fontpush(p, fontmap[(int)bp->font]); + if (bp->left) + term_word(p, bp->left); + if (EQN_SUBEXPR == bp->type) + term_word(p, "("); + + if (bp->text) + term_word(p, bp->text); + + if (bp->first) + eqn_box(p, bp->first); + + if (EQN_SUBEXPR == bp->type) + term_word(p, ")"); + if (bp->right) + term_word(p, bp->right); + if (EQNFONT_NONE != bp->font) + term_fontpop(p); + + if (bp->next) + eqn_box(p, bp->next); +} diff --git a/example.style.css b/example.style.css new file mode 100644 index 000000000000..660f4d132000 --- /dev/null +++ b/example.style.css @@ -0,0 +1,110 @@ +/* $Id: example.style.css,v 1.49 2011/12/15 12:18:57 kristaps Exp $ */ +/* + * This is an example style-sheet provided for mandoc(1) and the -Thtml + * or -Txhtml output mode. + * It mimics the appearance of the legacy man.cgi output. + * See mdoc(7) and man(7) for macro explanations. + */ + +div.mandoc { min-width: 102ex; + width: 102ex; + font-family: monospace; } /* This is the outer node of all mandoc -T[x]html documents. */ +div.mandoc h1 { margin-bottom: 0ex; font-size: inherit; margin-left: -4ex; } /* Section header (Sh, SH). */ +div.mandoc h2 { margin-bottom: 0ex; font-size: inherit; margin-left: -2ex; } /* Sub-section header (Ss, SS). */ +div.mandoc table { width: 100%; margin-top: 0ex; margin-bottom: 0ex; } /* All tables. */ +div.mandoc td { vertical-align: top; } /* All table cells. */ +div.mandoc p { } /* Paragraph: Pp, Lp. */ +div.mandoc blockquote { margin-left: 5ex; margin-top: 0ex; margin-bottom: 0ex; } /* D1, Dl. */ +div.mandoc div.section { margin-bottom: 2ex; margin-left: 5ex; } /* Sections (Sh, SH). */ +div.mandoc div.subsection { } /* Sub-sections (Ss, SS). */ +div.mandoc table.synopsis { } /* SYNOPSIS section table. */ +div.mandoc table.foot { } /* Document footer. */ +div.mandoc td.foot-date { width: 50%; } /* Document footer: date. */ +div.mandoc td.foot-os { width: 50%; text-align: right; } /* Document footer: OS/source. */ +div.mandoc table.head { } /* Document header. */ +div.mandoc td.head-ltitle { width: 10%; } /* Document header: left-title. */ +div.mandoc td.head-vol { width: 80%; text-align: center; } /* Document header: volume. */ +div.mandoc td.head-rtitle { width: 10%; text-align: right; } /* Document header: right-title. */ +div.mandoc .display { } /* All Bd, D1, Dl. */ +div.mandoc .list { } /* All Bl. */ +div.mandoc i { } /* Italic: BI, IB, I, (implicit). */ +div.mandoc b { } /* Bold: SB, BI, IB, BR, RB, B, (implicit). */ +div.mandoc small { } /* Small: SB, SM. */ +div.mandoc .emph { font-style: italic; font-weight: normal; } /* Emphasis: Em, Bl -emphasis. */ +div.mandoc .symb { font-style: normal; font-weight: bold; } /* Symbolic: Sy, Ms, Bf -symbolic. */ +div.mandoc .lit { font-style: normal; font-weight: normal; font-family: monospace; } /* Literal: Dl, Li, Ql, Bf -literal, Bl -literal, Bl -unfilled. */ +div.mandoc i.addr { font-weight: normal; } /* Address (Ad). */ +div.mandoc i.arg { font-weight: normal; } /* Command argument (Ar). */ +div.mandoc span.author { } /* Author name (An). */ +div.mandoc b.cmd { font-style: normal; } /* Command (Cm). */ +div.mandoc b.config { font-style: normal; } /* Config statement (Cd). */ +div.mandoc span.define { } /* Defines (Dv). */ +div.mandoc span.desc { } /* Nd. After em-dash. */ +div.mandoc b.diag { font-style: normal; } /* Diagnostic (Bl -diag). */ +div.mandoc span.env { } /* Environment variables (Ev). */ +div.mandoc span.errno { } /* Error string (Er). */ +div.mandoc i.farg { font-weight: normal; } /* Function argument (Fa, Fn). */ +div.mandoc i.file { font-weight: normal; } /* File (Pa). */ +div.mandoc b.flag { font-style: normal; } /* Flag (Fl, Cm). */ +div.mandoc b.fname { font-style: normal; } /* Function name (Fa, Fn, Rv). */ +div.mandoc i.ftype { font-weight: normal; } /* Function types (Ft, Fn). */ +div.mandoc b.includes { font-style: normal; } /* Header includes (In). */ +div.mandoc span.lib { } /* Library (Lb). */ +div.mandoc i.link-sec { font-weight: normal; } /* Section links (Sx). */ +div.mandoc b.macro { font-style: normal; } /* Macro-ish thing (Fd). */ +div.mandoc b.name { font-style: normal; } /* Name of utility (Nm). */ +div.mandoc span.opt { } /* Options (Op, Oo/Oc). */ +div.mandoc span.ref { } /* Citations (Rs). */ +div.mandoc span.ref-auth { } /* Reference author (%A). */ +div.mandoc i.ref-book { font-weight: normal; } /* Reference book (%B). */ +div.mandoc span.ref-city { } /* Reference city (%C). */ +div.mandoc span.ref-date { } /* Reference date (%D). */ +div.mandoc i.ref-issue { font-weight: normal; } /* Reference issuer/publisher (%I). */ +div.mandoc i.ref-jrnl { font-weight: normal; } /* Reference journal (%J). */ +div.mandoc span.ref-num { } /* Reference number (%N). */ +div.mandoc span.ref-opt { } /* Reference optionals (%O). */ +div.mandoc span.ref-page { } /* Reference page (%P). */ +div.mandoc span.ref-corp { } /* Reference corporate/foreign author (%Q). */ +div.mandoc span.ref-rep { } /* Reference report (%R). */ +div.mandoc span.ref-title { text-decoration: underline; } /* Reference title (%T). */ +div.mandoc span.ref-vol { } /* Reference volume (%V). */ +div.mandoc span.type { font-style: italic; font-weight: normal; } /* Variable types (Vt). */ +div.mandoc span.unix { } /* Unices (Ux, Ox, Nx, Fx, Bx, Bsx, Dx). */ +div.mandoc b.utility { font-style: normal; } /* Name of utility (Ex). */ +div.mandoc b.var { font-style: normal; } /* Variables (Rv). */ +div.mandoc a.link-ext { } /* Off-site link (Lk). */ +div.mandoc a.link-includes { } /* Include-file link (In). */ +div.mandoc a.link-mail { } /* Mailto links (Mt). */ +div.mandoc a.link-man { } /* Manual links (Xr). */ +div.mandoc a.link-ref { } /* Reference section links (%Q). */ +div.mandoc a.link-sec { } /* Section links (Sx). */ +div.mandoc dl.list-diag { } /* Formatting for lists. See mdoc(7). */ +div.mandoc dt.list-diag { } +div.mandoc dd.list-diag { } +div.mandoc dl.list-hang { } +div.mandoc dt.list-hang { } +div.mandoc dd.list-hang { } +div.mandoc dl.list-inset { } +div.mandoc dt.list-inset { } +div.mandoc dd.list-inset { } +div.mandoc dl.list-ohang { } +div.mandoc dt.list-ohang { } +div.mandoc dd.list-ohang { margin-left: 0ex; } +div.mandoc dl.list-tag { } +div.mandoc dt.list-tag { } +div.mandoc dd.list-tag { } +div.mandoc table.list-col { } +div.mandoc tr.list-col { } +div.mandoc td.list-col { } +div.mandoc ul.list-bul { list-style-type: disc; padding-left: 1em; } +div.mandoc li.list-bul { } +div.mandoc ul.list-dash { list-style-type: none; padding-left: 0em; } +div.mandoc li.list-dash:before { content: "\2014 "; } +div.mandoc ul.list-hyph { list-style-type: none; padding-left: 0em; } +div.mandoc li.list-hyph:before { content: "\2013 "; } +div.mandoc ul.list-item { list-style-type: none; padding-left: 0em; } +div.mandoc li.list-item { } +div.mandoc ol.list-enum { padding-left: 2em; } +div.mandoc li.list-enum { } +div.mandoc span.eqn { } /* Equation modes. See eqn(7). */ +div.mandoc table.tbl { } /* Table modes. See tbl(7). */ diff --git a/external.png b/external.png Binary files differnew file mode 100644 index 000000000000..419c06fb960b --- /dev/null +++ b/external.png diff --git a/html.c b/html.c new file mode 100644 index 000000000000..326df035fc44 --- /dev/null +++ b/html.c @@ -0,0 +1,699 @@ +/* $Id: html.c,v 1.150 2011/10/05 21:35:17 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/types.h> + +#include <assert.h> +#include <ctype.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "mandoc.h" +#include "libmandoc.h" +#include "out.h" +#include "html.h" +#include "main.h" + +struct htmldata { + const char *name; + int flags; +#define HTML_CLRLINE (1 << 0) +#define HTML_NOSTACK (1 << 1) +#define HTML_AUTOCLOSE (1 << 2) /* Tag has auto-closure. */ +}; + +static const struct htmldata htmltags[TAG_MAX] = { + {"html", HTML_CLRLINE}, /* TAG_HTML */ + {"head", HTML_CLRLINE}, /* TAG_HEAD */ + {"body", HTML_CLRLINE}, /* TAG_BODY */ + {"meta", HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_META */ + {"title", HTML_CLRLINE}, /* TAG_TITLE */ + {"div", HTML_CLRLINE}, /* TAG_DIV */ + {"h1", 0}, /* TAG_H1 */ + {"h2", 0}, /* TAG_H2 */ + {"span", 0}, /* TAG_SPAN */ + {"link", HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_LINK */ + {"br", HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_BR */ + {"a", 0}, /* TAG_A */ + {"table", HTML_CLRLINE}, /* TAG_TABLE */ + {"tbody", HTML_CLRLINE}, /* TAG_TBODY */ + {"col", HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_COL */ + {"tr", HTML_CLRLINE}, /* TAG_TR */ + {"td", HTML_CLRLINE}, /* TAG_TD */ + {"li", HTML_CLRLINE}, /* TAG_LI */ + {"ul", HTML_CLRLINE}, /* TAG_UL */ + {"ol", HTML_CLRLINE}, /* TAG_OL */ + {"dl", HTML_CLRLINE}, /* TAG_DL */ + {"dt", HTML_CLRLINE}, /* TAG_DT */ + {"dd", HTML_CLRLINE}, /* TAG_DD */ + {"blockquote", HTML_CLRLINE}, /* TAG_BLOCKQUOTE */ + {"p", HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_P */ + {"pre", HTML_CLRLINE }, /* TAG_PRE */ + {"b", 0 }, /* TAG_B */ + {"i", 0 }, /* TAG_I */ + {"code", 0 }, /* TAG_CODE */ + {"small", 0 }, /* TAG_SMALL */ +}; + +static const char *const htmlattrs[ATTR_MAX] = { + "http-equiv", /* ATTR_HTTPEQUIV */ + "content", /* ATTR_CONTENT */ + "name", /* ATTR_NAME */ + "rel", /* ATTR_REL */ + "href", /* ATTR_HREF */ + "type", /* ATTR_TYPE */ + "media", /* ATTR_MEDIA */ + "class", /* ATTR_CLASS */ + "style", /* ATTR_STYLE */ + "width", /* ATTR_WIDTH */ + "id", /* ATTR_ID */ + "summary", /* ATTR_SUMMARY */ + "align", /* ATTR_ALIGN */ + "colspan", /* ATTR_COLSPAN */ +}; + +static const char *const roffscales[SCALE_MAX] = { + "cm", /* SCALE_CM */ + "in", /* SCALE_IN */ + "pc", /* SCALE_PC */ + "pt", /* SCALE_PT */ + "em", /* SCALE_EM */ + "em", /* SCALE_MM */ + "ex", /* SCALE_EN */ + "ex", /* SCALE_BU */ + "em", /* SCALE_VS */ + "ex", /* SCALE_FS */ +}; + +static void bufncat(struct html *, const char *, size_t); +static void print_ctag(struct html *, enum htmltag); +static int print_encode(struct html *, const char *, int); +static void print_metaf(struct html *, enum mandoc_esc); +static void print_attr(struct html *, const char *, const char *); +static void *ml_alloc(char *, enum htmltype); + +static void * +ml_alloc(char *outopts, enum htmltype type) +{ + struct html *h; + const char *toks[5]; + char *v; + + toks[0] = "style"; + toks[1] = "man"; + toks[2] = "includes"; + toks[3] = "fragment"; + toks[4] = NULL; + + h = mandoc_calloc(1, sizeof(struct html)); + + h->type = type; + h->tags.head = NULL; + h->symtab = mchars_alloc(); + + while (outopts && *outopts) + switch (getsubopt(&outopts, UNCONST(toks), &v)) { + case (0): + h->style = v; + break; + case (1): + h->base_man = v; + break; + case (2): + h->base_includes = v; + break; + case (3): + h->oflags |= HTML_FRAGMENT; + break; + default: + break; + } + + return(h); +} + +void * +html_alloc(char *outopts) +{ + + return(ml_alloc(outopts, HTML_HTML_4_01_STRICT)); +} + + +void * +xhtml_alloc(char *outopts) +{ + + return(ml_alloc(outopts, HTML_XHTML_1_0_STRICT)); +} + + +void +html_free(void *p) +{ + struct tag *tag; + struct html *h; + + h = (struct html *)p; + + while ((tag = h->tags.head) != NULL) { + h->tags.head = tag->next; + free(tag); + } + + if (h->symtab) + mchars_free(h->symtab); + + free(h); +} + + +void +print_gen_head(struct html *h) +{ + struct htmlpair tag[4]; + + tag[0].key = ATTR_HTTPEQUIV; + tag[0].val = "Content-Type"; + tag[1].key = ATTR_CONTENT; + tag[1].val = "text/html; charset=utf-8"; + print_otag(h, TAG_META, 2, tag); + + tag[0].key = ATTR_NAME; + tag[0].val = "resource-type"; + tag[1].key = ATTR_CONTENT; + tag[1].val = "document"; + print_otag(h, TAG_META, 2, tag); + + if (h->style) { + tag[0].key = ATTR_REL; + tag[0].val = "stylesheet"; + tag[1].key = ATTR_HREF; + tag[1].val = h->style; + tag[2].key = ATTR_TYPE; + tag[2].val = "text/css"; + tag[3].key = ATTR_MEDIA; + tag[3].val = "all"; + print_otag(h, TAG_LINK, 4, tag); + } +} + +static void +print_metaf(struct html *h, enum mandoc_esc deco) +{ + enum htmlfont font; + + switch (deco) { + case (ESCAPE_FONTPREV): + font = h->metal; + break; + case (ESCAPE_FONTITALIC): + font = HTMLFONT_ITALIC; + break; + case (ESCAPE_FONTBOLD): + font = HTMLFONT_BOLD; + break; + case (ESCAPE_FONT): + /* FALLTHROUGH */ + case (ESCAPE_FONTROMAN): + font = HTMLFONT_NONE; + break; + default: + abort(); + /* NOTREACHED */ + } + + if (h->metaf) { + print_tagq(h, h->metaf); + h->metaf = NULL; + } + + h->metal = h->metac; + h->metac = font; + + if (HTMLFONT_NONE != font) + h->metaf = HTMLFONT_BOLD == font ? + print_otag(h, TAG_B, 0, NULL) : + print_otag(h, TAG_I, 0, NULL); +} + +int +html_strlen(const char *cp) +{ + int ssz, sz; + const char *seq, *p; + + /* + * Account for escaped sequences within string length + * calculations. This follows the logic in term_strlen() as we + * must calculate the width of produced strings. + * Assume that characters are always width of "1". This is + * hacky, but it gets the job done for approximation of widths. + */ + + sz = 0; + while (NULL != (p = strchr(cp, '\\'))) { + sz += (int)(p - cp); + ++cp; + switch (mandoc_escape(&cp, &seq, &ssz)) { + case (ESCAPE_ERROR): + return(sz); + case (ESCAPE_UNICODE): + /* FALLTHROUGH */ + case (ESCAPE_NUMBERED): + /* FALLTHROUGH */ + case (ESCAPE_SPECIAL): + sz++; + break; + default: + break; + } + } + + assert(sz >= 0); + return(sz + strlen(cp)); +} + +static int +print_encode(struct html *h, const char *p, int norecurse) +{ + size_t sz; + int c, len, nospace; + const char *seq; + enum mandoc_esc esc; + static const char rejs[6] = { '\\', '<', '>', '&', ASCII_HYPH, '\0' }; + + nospace = 0; + + while ('\0' != *p) { + sz = strcspn(p, rejs); + + fwrite(p, 1, sz, stdout); + p += (int)sz; + + if ('\0' == *p) + break; + + switch (*p++) { + case ('<'): + printf("<"); + continue; + case ('>'): + printf(">"); + continue; + case ('&'): + printf("&"); + continue; + case (ASCII_HYPH): + putchar('-'); + continue; + default: + break; + } + + esc = mandoc_escape(&p, &seq, &len); + if (ESCAPE_ERROR == esc) + break; + + switch (esc) { + case (ESCAPE_UNICODE): + /* Skip passed "u" header. */ + c = mchars_num2uc(seq + 1, len - 1); + if ('\0' != c) + printf("&#x%x;", c); + break; + case (ESCAPE_NUMBERED): + c = mchars_num2char(seq, len); + if ('\0' != c) + putchar(c); + break; + case (ESCAPE_SPECIAL): + c = mchars_spec2cp(h->symtab, seq, len); + if (c > 0) + printf("&#%d;", c); + else if (-1 == c && 1 == len) + putchar((int)*seq); + break; + case (ESCAPE_FONT): + /* FALLTHROUGH */ + case (ESCAPE_FONTPREV): + /* FALLTHROUGH */ + case (ESCAPE_FONTBOLD): + /* FALLTHROUGH */ + case (ESCAPE_FONTITALIC): + /* FALLTHROUGH */ + case (ESCAPE_FONTROMAN): + if (norecurse) + break; + print_metaf(h, esc); + break; + case (ESCAPE_NOSPACE): + if ('\0' == *p) + nospace = 1; + break; + default: + break; + } + } + + return(nospace); +} + + +static void +print_attr(struct html *h, const char *key, const char *val) +{ + printf(" %s=\"", key); + (void)print_encode(h, val, 1); + putchar('\"'); +} + + +struct tag * +print_otag(struct html *h, enum htmltag tag, + int sz, const struct htmlpair *p) +{ + int i; + struct tag *t; + + /* Push this tags onto the stack of open scopes. */ + + if ( ! (HTML_NOSTACK & htmltags[tag].flags)) { + t = mandoc_malloc(sizeof(struct tag)); + t->tag = tag; + t->next = h->tags.head; + h->tags.head = t; + } else + t = NULL; + + if ( ! (HTML_NOSPACE & h->flags)) + if ( ! (HTML_CLRLINE & htmltags[tag].flags)) { + /* Manage keeps! */ + if ( ! (HTML_KEEP & h->flags)) { + if (HTML_PREKEEP & h->flags) + h->flags |= HTML_KEEP; + putchar(' '); + } else + printf(" "); + } + + if ( ! (h->flags & HTML_NONOSPACE)) + h->flags &= ~HTML_NOSPACE; + else + h->flags |= HTML_NOSPACE; + + /* Print out the tag name and attributes. */ + + printf("<%s", htmltags[tag].name); + for (i = 0; i < sz; i++) + print_attr(h, htmlattrs[p[i].key], p[i].val); + + /* Add non-overridable attributes. */ + + if (TAG_HTML == tag && HTML_XHTML_1_0_STRICT == h->type) { + print_attr(h, "xmlns", "http://www.w3.org/1999/xhtml"); + print_attr(h, "xml:lang", "en"); + print_attr(h, "lang", "en"); + } + + /* Accommodate for XML "well-formed" singleton escaping. */ + + if (HTML_AUTOCLOSE & htmltags[tag].flags) + switch (h->type) { + case (HTML_XHTML_1_0_STRICT): + putchar('/'); + break; + default: + break; + } + + putchar('>'); + + h->flags |= HTML_NOSPACE; + + if ((HTML_AUTOCLOSE | HTML_CLRLINE) & htmltags[tag].flags) + putchar('\n'); + + return(t); +} + + +static void +print_ctag(struct html *h, enum htmltag tag) +{ + + printf("</%s>", htmltags[tag].name); + if (HTML_CLRLINE & htmltags[tag].flags) { + h->flags |= HTML_NOSPACE; + putchar('\n'); + } +} + +void +print_gen_decls(struct html *h) +{ + const char *doctype; + const char *dtd; + const char *name; + + switch (h->type) { + case (HTML_HTML_4_01_STRICT): + name = "HTML"; + doctype = "-//W3C//DTD HTML 4.01//EN"; + dtd = "http://www.w3.org/TR/html4/strict.dtd"; + break; + default: + puts("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); + name = "html"; + doctype = "-//W3C//DTD XHTML 1.0 Strict//EN"; + dtd = "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"; + break; + } + + printf("<!DOCTYPE %s PUBLIC \"%s\" \"%s\">\n", + name, doctype, dtd); +} + +void +print_text(struct html *h, const char *word) +{ + + if ( ! (HTML_NOSPACE & h->flags)) { + /* Manage keeps! */ + if ( ! (HTML_KEEP & h->flags)) { + if (HTML_PREKEEP & h->flags) + h->flags |= HTML_KEEP; + putchar(' '); + } else + printf(" "); + } + + assert(NULL == h->metaf); + if (HTMLFONT_NONE != h->metac) + h->metaf = HTMLFONT_BOLD == h->metac ? + print_otag(h, TAG_B, 0, NULL) : + print_otag(h, TAG_I, 0, NULL); + + assert(word); + if ( ! print_encode(h, word, 0)) { + if ( ! (h->flags & HTML_NONOSPACE)) + h->flags &= ~HTML_NOSPACE; + } else + h->flags |= HTML_NOSPACE; + + if (h->metaf) { + print_tagq(h, h->metaf); + h->metaf = NULL; + } + + h->flags &= ~HTML_IGNDELIM; +} + + +void +print_tagq(struct html *h, const struct tag *until) +{ + struct tag *tag; + + while ((tag = h->tags.head) != NULL) { + /* + * Remember to close out and nullify the current + * meta-font and table, if applicable. + */ + if (tag == h->metaf) + h->metaf = NULL; + if (tag == h->tblt) + h->tblt = NULL; + print_ctag(h, tag->tag); + h->tags.head = tag->next; + free(tag); + if (until && tag == until) + return; + } +} + + +void +print_stagq(struct html *h, const struct tag *suntil) +{ + struct tag *tag; + + while ((tag = h->tags.head) != NULL) { + if (suntil && tag == suntil) + return; + /* + * Remember to close out and nullify the current + * meta-font and table, if applicable. + */ + if (tag == h->metaf) + h->metaf = NULL; + if (tag == h->tblt) + h->tblt = NULL; + print_ctag(h, tag->tag); + h->tags.head = tag->next; + free(tag); + } +} + +void +bufinit(struct html *h) +{ + + h->buf[0] = '\0'; + h->buflen = 0; +} + +void +bufcat_style(struct html *h, const char *key, const char *val) +{ + + bufcat(h, key); + bufcat(h, ":"); + bufcat(h, val); + bufcat(h, ";"); +} + +void +bufcat(struct html *h, const char *p) +{ + + h->buflen = strlcat(h->buf, p, BUFSIZ); + assert(h->buflen < BUFSIZ); +} + +void +bufcat_fmt(struct html *h, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + (void)vsnprintf(h->buf + (int)h->buflen, + BUFSIZ - h->buflen - 1, fmt, ap); + va_end(ap); + h->buflen = strlen(h->buf); +} + +static void +bufncat(struct html *h, const char *p, size_t sz) +{ + + assert(h->buflen + sz + 1 < BUFSIZ); + strncat(h->buf, p, sz); + h->buflen += sz; +} + +void +buffmt_includes(struct html *h, const char *name) +{ + const char *p, *pp; + + pp = h->base_includes; + + bufinit(h); + while (NULL != (p = strchr(pp, '%'))) { + bufncat(h, pp, (size_t)(p - pp)); + switch (*(p + 1)) { + case('I'): + bufcat(h, name); + break; + default: + bufncat(h, p, 2); + break; + } + pp = p + 2; + } + if (pp) + bufcat(h, pp); +} + +void +buffmt_man(struct html *h, + const char *name, const char *sec) +{ + const char *p, *pp; + + pp = h->base_man; + + bufinit(h); + while (NULL != (p = strchr(pp, '%'))) { + bufncat(h, pp, (size_t)(p - pp)); + switch (*(p + 1)) { + case('S'): + bufcat(h, sec ? sec : "1"); + break; + case('N'): + bufcat_fmt(h, name); + break; + default: + bufncat(h, p, 2); + break; + } + pp = p + 2; + } + if (pp) + bufcat(h, pp); +} + +void +bufcat_su(struct html *h, const char *p, const struct roffsu *su) +{ + double v; + + v = su->scale; + if (SCALE_MM == su->unit && 0.0 == (v /= 100.0)) + v = 1.0; + + bufcat_fmt(h, "%s: %.2f%s;", p, v, roffscales[su->unit]); +} + +void +bufcat_id(struct html *h, const char *src) +{ + + /* Cf. <http://www.w3.org/TR/html4/types.html#h-6.2>. */ + + while ('\0' != *src) + bufcat_fmt(h, "%.2x", *src++); +} diff --git a/html.h b/html.h new file mode 100644 index 000000000000..60960702f19b --- /dev/null +++ b/html.h @@ -0,0 +1,164 @@ +/* $Id: html.h,v 1.47 2011/10/05 21:35:17 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef HTML_H +#define HTML_H + +__BEGIN_DECLS + +enum htmltag { + TAG_HTML, + TAG_HEAD, + TAG_BODY, + TAG_META, + TAG_TITLE, + TAG_DIV, + TAG_H1, + TAG_H2, + TAG_SPAN, + TAG_LINK, + TAG_BR, + TAG_A, + TAG_TABLE, + TAG_TBODY, + TAG_COL, + TAG_TR, + TAG_TD, + TAG_LI, + TAG_UL, + TAG_OL, + TAG_DL, + TAG_DT, + TAG_DD, + TAG_BLOCKQUOTE, + TAG_P, + TAG_PRE, + TAG_B, + TAG_I, + TAG_CODE, + TAG_SMALL, + TAG_MAX +}; + +enum htmlattr { + ATTR_HTTPEQUIV, + ATTR_CONTENT, + ATTR_NAME, + ATTR_REL, + ATTR_HREF, + ATTR_TYPE, + ATTR_MEDIA, + ATTR_CLASS, + ATTR_STYLE, + ATTR_WIDTH, + ATTR_ID, + ATTR_SUMMARY, + ATTR_ALIGN, + ATTR_COLSPAN, + ATTR_MAX +}; + +enum htmlfont { + HTMLFONT_NONE = 0, + HTMLFONT_BOLD, + HTMLFONT_ITALIC, + HTMLFONT_MAX +}; + +struct tag { + struct tag *next; + enum htmltag tag; +}; + +struct tagq { + struct tag *head; +}; + +struct htmlpair { + enum htmlattr key; + const char *val; +}; + +#define PAIR_INIT(p, t, v) \ + do { \ + (p)->key = (t); \ + (p)->val = (v); \ + } while (/* CONSTCOND */ 0) + +#define PAIR_ID_INIT(p, v) PAIR_INIT(p, ATTR_ID, v) +#define PAIR_CLASS_INIT(p, v) PAIR_INIT(p, ATTR_CLASS, v) +#define PAIR_HREF_INIT(p, v) PAIR_INIT(p, ATTR_HREF, v) +#define PAIR_STYLE_INIT(p, h) PAIR_INIT(p, ATTR_STYLE, (h)->buf) +#define PAIR_SUMMARY_INIT(p, v) PAIR_INIT(p, ATTR_SUMMARY, v) + +enum htmltype { + HTML_HTML_4_01_STRICT, + HTML_XHTML_1_0_STRICT +}; + +struct html { + int flags; +#define HTML_NOSPACE (1 << 0) /* suppress next space */ +#define HTML_IGNDELIM (1 << 1) +#define HTML_KEEP (1 << 2) +#define HTML_PREKEEP (1 << 3) +#define HTML_NONOSPACE (1 << 4) /* never add spaces */ +#define HTML_LITERAL (1 << 5) /* literal (e.g., <PRE>) context */ + struct tagq tags; /* stack of open tags */ + struct rofftbl tbl; /* current table */ + struct tag *tblt; /* current open table scope */ + struct mchars *symtab; /* character-escapes */ + char *base_man; /* base for manpage href */ + char *base_includes; /* base for include href */ + char *style; /* style-sheet URI */ + char buf[BUFSIZ]; /* see bufcat and friends */ + size_t buflen; + struct tag *metaf; /* current open font scope */ + enum htmlfont metal; /* last used font */ + enum htmlfont metac; /* current font mode */ + enum htmltype type; /* output media type */ + int oflags; /* output options */ +#define HTML_FRAGMENT (1 << 0) /* don't emit HTML/HEAD/BODY */ +}; + +void print_gen_decls(struct html *); +void print_gen_head(struct html *); +struct tag *print_otag(struct html *, enum htmltag, + int, const struct htmlpair *); +void print_tagq(struct html *, const struct tag *); +void print_stagq(struct html *, const struct tag *); +void print_text(struct html *, const char *); +void print_tblclose(struct html *); +void print_tbl(struct html *, const struct tbl_span *); +void print_eqn(struct html *, const struct eqn *); + +void bufcat_fmt(struct html *, const char *, ...); +void bufcat(struct html *, const char *); +void bufcat_id(struct html *, const char *); +void bufcat_style(struct html *, + const char *, const char *); +void bufcat_su(struct html *, const char *, + const struct roffsu *); +void bufinit(struct html *); +void buffmt_man(struct html *, + const char *, const char *); +void buffmt_includes(struct html *, const char *); + +int html_strlen(const char *); + +__END_DECLS + +#endif /*!HTML_H*/ diff --git a/index.css b/index.css new file mode 100644 index 000000000000..d98316eaf311 --- /dev/null +++ b/index.css @@ -0,0 +1,48 @@ +html { min-width: 40em; + margin-top: 2em; + margin-left: auto; + margin-right: auto; + width: 80%; } + +body { text-align: justify; + font-family: Helvetica,Arial,sans-serif; + line-height: 120%; + font-size: small; } + +p,ul,table { margin-left: 3em; } + +p.head, +p.subhead, +p.foot { margin-left: 0.0em; margin-right: 0.0em; } + +p.news { margin-left: 2.0em; } + +li { margin: 0.25em; } + +h1 { font-size: 110%; } +h2 { font-size: 105%; margin-left: 1.5em } + +p.head { margin-bottom: 0.5em; + border-bottom: 1px solid #dddddd; + padding-bottom: 0.2em; } + +p.subhead { margin-top: 0em; + margin-bottom: 1.75em; } + +p.foot { border-top: 1px solid #dddddd; + color: #666666; + padding-top: 0.2em; + margin-top: 1.75em; } + +span.nm { color: green; } + +span.file { font-style: italic; } + +span.attn { font-weight: bold; } + +span.flag { font-weight: bold; } + +a { text-decoration: none; } + +a.external { background: transparent url(external.png) center right no-repeat; + padding-right: 12px; } diff --git a/index.sgml b/index.sgml new file mode 100644 index 000000000000..4386a9ebd955 --- /dev/null +++ b/index.sgml @@ -0,0 +1,364 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> +<HTML> + <HEAD> + <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8"> + <LINK REL="stylesheet" HREF="index.css" TYPE="text/css" MEDIA="all"> + <TITLE>mdocml | UNIX manpage compiler</TITLE> + </HEAD> + <BODY> + <P CLASS="head"> + <B>mdocml</B> – UNIX manpage compiler, current version @VERSION@ (@VDATE@) + </P> + <P CLASS="subhead"> + Sources: <A HREF="/snapshots/mdocml.tar.gz">current</A>, + <A HREF="/cgi-bin/cvsweb/?cvsroot=mdocml">cvsweb</A> + (<A HREF="/snapshots/">archives</A>) + </P> + <H1> + <A NAME="description">Description</A> + </H1> + <P> + <SPAN CLASS="nm">mdocml</SPAN> is a suite of tools compiling <I><A HREF="mdoc.7.html">mdoc</A></I>, the roff macro + package of choice for BSD manual pages, and <I><A HREF="man.7.html">man</A></I>, the predominant historical package for + UNIX manuals. The mission of <SPAN CLASS="nm">mdocml</SPAN> is to deprecate <A + HREF="http://www.gnu.org/software/groff/" CLASS="external">groff</A>, the GNU troff implementation, for displaying <I>mdoc</I> + pages whilst providing token support for <I>man</I>. + </P> + <P> + Why? groff amounts to over 5 MB of source code, most of which is C++ and all of which is GPL. It runs slowly, produces + uncertain output, and varies in operation from system to system. mdocml strives to fix this (respectively small, C, <A + CLASS="external" HREF="http://www.isc.org/software/license">ISC</A>-licensed, fast and regular). + </P> + <P> + <SPAN CLASS="nm">mdocml</SPAN> consists of the <A HREF="mandoc.3.html">libmandoc</A> validating compiler and <A + HREF="mandoc.1.html">mandoc</A>, which interfaces with the compiler library to format output for UNIX terminals (with + support for wide-character locales), XHTML, HTML, PostScript, and PDF. + It also includes <A HREF="preconv.1.html">preconv</A>, for recoding multibyte manuals; + <A HREF="demandoc.1.html">demandoc</A>, for emitting only text parts of manuals; + <A HREF="mandocdb.8.html">mandocdb</A>, for indexing manuals; and + <A HREF="apropos.1.html">apropos</A>, <A HREF="whatis.1.html">whatis</A>, and + <A HREF="man.cgi.7.html">man.cgi</A> (via <A HREF="catman.8.html">catman</A>) for semantic search of manual content. + It is a <A CLASS="external" HREF="http://bsd.lv/">BSD.lv</A> project. + </P> + <P> + <I>Disambiguation</I>: <SPAN CLASS="nm">mdocml</SPAN> is often referred to by its installed binary, <Q>mandoc</Q>. + </P> + <H2> + <A NAME="sources">Sources</A> + </H2> + <P> + <SPAN CLASS="nm">mdocml</SPAN> is in plain-old ANSI C and should build and run on any modern system; however, you'll + need <A HREF="http://www.oracle.com/technetwork/database/berkeleydb/overview/index.html">libdb</A> to build <A + HREF="apropos.1.html">apropos</A>, <A HREF="whatis.1.html">whatis</A>, <A HREF="man.cgi.7.html">man.cgi</A>, <A + HREF="catman.8.html">catman</A>, and <A HREF="mandocdb.8.html">mandocdb</A> (this is installed by default on BSD UNIX + systems — see the <I>Makefile</I> if you're running Linux). To build and install into <I>/usr/local/</I>, just + run <CODE>make install</CODE>. Be careful: the <B>preconv</B>, <B>apropos</B>, and <B>whatis</B> binary names are + usually taken by existing utilities. + </P> + <H2> + <A NAME="binaries">Binaries</A> + </H2> + <P> + Binary archives consist of pre-compiled binaries, manuals, and other necessary files. + Universal (Mac OS X) binaries are compiled for the PCC, i386, and x86_64 architectures. + Windows binaries are compiled with <A CLASS="external" HREF="http://www.mingw.org">MingW</A> for the 32-bit (i686) and + 64-bit (x86_64) architectures. + </P> + <H2> + Downstream + </H2> + <P> + Several systems come bundled with <SPAN CLASS="nm">mdocml</SPAN> utilities. + If your system does not appear below, the maintainers have not contacted me and it should not be considered + <Q>official</Q>. + Please <A HREF="#contact">contact us</A> if you plan on maintaining a downstream version! + </P> + <TABLE WIDTH="100%" SUMMARY="Downstream Sources"> + <COL WIDTH="175"> + <COL> + <TBODY> + <TR> + <TD>DragonFly BSD</TD> + <TD> + <A HREF="http://gitweb.dragonflybsd.org/dragonfly.git/tree/HEAD:/usr.bin/mandoc" CLASS="external">usr.bin/mandoc</A> + </TD> + </TR> + <TR> + <TD>FreeBSD</TD> + <TD> + <A HREF="http://www.freebsd.org/cgi/cvsweb.cgi/ports/textproc/mdocml/" CLASS="external">ports/textproc/mdocml</A> + </TD> + </TR> + <TR> + <TD>NetBSD</TD> + <TD> + <A HREF="http://cvsweb.netbsd.org/bsdweb.cgi/src/external/bsd/mdocml/" CLASS="external">src/external/bsd/mdocml</A> + </TD> + </TR> + <TR> + <TD>OpenBSD</TD> + <TD> + <A HREF="http://www.openbsd.org/cgi-bin/cvsweb/src/usr.bin/mandoc/" CLASS="external">src/usr.bin/mandoc</A> + </TD> + </TR> + <TR> + <TD>pkgsrc</TD> + <TD> + <A HREF="http://pkgsrc.se/textproc/mdocml" CLASS="external">textproc/mdocml</A> + </TD> + </TR> + <TR> + <TD>Minix3</TD> + <TD> + <A HREF="http://git.minix3.org/?p=minix.git;a=tree;f=external/bsd/mdocml" CLASS="external">external/bsd/mdocml</A> + </TD> + </TR> + </TBODY> + </TABLE> + <H1> + <A NAME="documentation">Documentation</A> + </H1> + <P> + These manuals are generated automatically and refer to the current release. + They are the authoritative documentation for the <SPAN CLASS="nm">mdocml</SPAN> system. + </P> + + <TABLE WIDTH="100%" SUMMARY="Documentation"> + <COL WIDTH="175"> + <COL> + <TBODY> + <TR> + <TD VALIGN="top"><A HREF="apropos.1.html">apropos(1)</A></TD> + <TD VALIGN="top"> + search the manual page database + (<A HREF="apropos.1.txt">text</A> | + <A HREF="apropos.1.xhtml">xhtml</A> | + <A HREF="apropos.1.pdf">pdf</A> | + <A HREF="apropos.1.ps">ps</A>) + </TD> + </TR> + <TR> + <TD VALIGN="top"><A HREF="demandoc.1.html">demandoc(1)</A></TD> + <TD VALIGN="top"> + emit only text of UNIX manuals + (<A HREF="demandoc.1.txt">text</A> | + <A HREF="demandoc.1.xhtml">xhtml</A> | + <A HREF="demandoc.1.pdf">pdf</A> | + <A HREF="demandoc.1.ps">ps</A>) + </TD> + </TR> + <TR> + <TD VALIGN="top"><A HREF="mandoc.1.html">mandoc(1)</A></TD> + <TD VALIGN="top"> + format and display UNIX manuals + (<A HREF="mandoc.1.txt">text</A> | + <A HREF="mandoc.1.xhtml">xhtml</A> | + <A HREF="mandoc.1.pdf">pdf</A> | + <A HREF="mandoc.1.ps">ps</A>) + </TD> + </TR> + <TR> + <TD VALIGN="top"><A HREF="preconv.1.html">preconv(1)</A></TD> + <TD VALIGN="top"> + recode multibyte UNIX manuals + (<A HREF="preconv.1.txt">text</A> | + <A HREF="preconv.1.xhtml">xhtml</A> | + <A HREF="preconv.1.pdf">pdf</A> | + <A HREF="preconv.1.ps">ps</A>) + </TD> + </TR> + <TR> + <TD VALIGN="top"><A HREF="whatis.1.html">whatis(1)</A></TD> + <TD VALIGN="top"> + search the manual page database + (<A HREF="whatis.1.txt">text</A> | + <A HREF="whatis.1.xhtml">xhtml</A> | + <A HREF="whatis.1.pdf">pdf</A> | + <A HREF="whatis.1.ps">ps</A>) + </TD> + </TR> + <TR> + <TD VALIGN="top"><A HREF="mandoc.3.html">mandoc(3)</A></TD> + <TD VALIGN="top"> + mandoc macro compiler library + (<A HREF="mandoc.3.txt">text</A> | + <A HREF="mandoc.3.xhtml">xhtml</A> | + <A HREF="mandoc.3.pdf">pdf</A> | + <A HREF="mandoc.3.ps">ps</A>) + </TD> + </TR> + <TR> + <TD VALIGN="top"><A HREF="man.7.html">man(7)</A></TD> + <TD VALIGN="top"> + man language reference + (<A HREF="man.7.txt">text</A> | + <A HREF="man.7.xhtml">xhtml</A> | + <A HREF="man.7.pdf">pdf</A> | + <A HREF="man.7.ps">ps</A>) + </TD> + </TR> + <TR> + <TD VALIGN="top"><A HREF="man.cgi.7.html">man.cgi(7)</A></TD> + <TD VALIGN="top"> + cgi for manpage query and display + (<A HREF="man.cgi.7.txt">text</A> | + <A HREF="man.cgi.7.xhtml">xhtml</A> | + <A HREF="man.cgi.7.pdf">pdf</A> | + <A HREF="man.cgi.7.ps">ps</A>) + </TD> + </TR> + <TR> + <TD VALIGN="top"><A HREF="eqn.7.html">eqn(7)</A></TD> + <TD VALIGN="top"> + eqn-mandoc language reference + (<A HREF="eqn.7.txt">text</A> | + <A HREF="eqn.7.xhtml">xhtml</A> | + <A HREF="eqn.7.pdf">pdf</A> | + <A HREF="eqn.7.ps">ps</A>) + </TD> + </TR> + <TR> + <TD VALIGN="top"><A HREF="mandoc_char.7.html">mandoc_char(7)</A></TD> + <TD VALIGN="top"> + mandoc special characters + (<A HREF="mandoc_char.7.txt">text</A> | + <A HREF="mandoc_char.7.xhtml">xhtml</A> | + <A HREF="mandoc_char.7.pdf">pdf</A> | + <A HREF="mandoc_char.7.ps">ps</A>) + </TD> + </TR> + <TR> + <TD VALIGN="top"><A HREF="mdoc.7.html">mdoc(7)</A></TD> + <TD VALIGN="top"> + mdoc language reference + (<A HREF="mdoc.7.txt">text</A> | + <A HREF="mdoc.7.xhtml">xhtml</A> | + <A HREF="mdoc.7.pdf">pdf</A> | + <A HREF="mdoc.7.ps">ps</A>) + </TD> + </TR> + <TR> + <TD VALIGN="top"><A HREF="roff.7.html">roff(7)</A></TD> + <TD VALIGN="top"> + roff-mandoc language reference + (<A HREF="roff.7.txt">text</A> | + <A HREF="roff.7.xhtml">xhtml</A> | + <A HREF="roff.7.pdf">pdf</A> | + <A HREF="roff.7.ps">ps</A>) + </TD> + </TR> + <TR> + <TD VALIGN="top"><A HREF="tbl.7.html">tbl(7)</A></TD> + <TD VALIGN="top"> + tbl-mandoc language reference + (<A HREF="tbl.7.txt">text</A> | + <A HREF="tbl.7.xhtml">xhtml</A> | + <A HREF="tbl.7.pdf">pdf</A> | + <A HREF="tbl.7.ps">ps</A>) + </TD> + </TR> + <TR> + <TD VALIGN="top"><A HREF="catman.8.html">catman(8)</A></TD> + <TD VALIGN="top"> + update a man.cgi manpage cache + (<A HREF="catman.8.txt">text</A> | + <A HREF="catman.8.xhtml">xhtml</A> | + <A HREF="catman.8.pdf">pdf</A> | + <A HREF="catman.8.ps">ps</A>) + </TD> + </TR> + <TR> + <TD VALIGN="top"><A HREF="mandocdb.8.html">mandocdb(8)</A></TD> + <TD VALIGN="top"> + index UNIX manuals + (<A HREF="mandocdb.8.txt">text</A> | + <A HREF="mandocdb.8.xhtml">xhtml</A> | + <A HREF="mandocdb.8.pdf">pdf</A> | + <A HREF="mandocdb.8.ps">ps</A>) + </TD> + </TR> + </TBODY> + </TABLE> + <H1> + <A NAME="contact">Contact</A> + </H1> + <P> + Use the mailing lists for bug-reports, patches, questions, etc. Please check the + <A HREF="http://mdocml.bsd.lv/cgi-bin/cvsweb/TODO?cvsroot=mdocml">TODO</A> for known issues + before posting. All lists are subscription-only: send a blank e-mail to the listed address to subscribe. Beyond that, + contact Kristaps at <A HREF="http://mailhide.recaptcha.net/d?k=01M6h_w7twDp58ZgH57eWC_w==&c=Q2DBUt401ePlSeupJFrq_Q==" TITLE="Reveal + this e-mail address">kris...</A>@bsd.lv. Archives are available at <A HREF="http://gmane.org/" CLASS="external">Gmane</A>. + </P> + <TABLE WIDTH="100%" SUMMARY="Mailing Lists"> + <COL WIDTH="175"> + <COL> + <TBODY> + <TR> + <TD> + disc<A CLASS="external" TITLE="Reveal this e-mail address" + HREF="http://www.google.com/recaptcha/mailhide/d?k=01KQ80PFH5n3BBNpF5Gs4sRg==&c=EV1QytpQqTHSItc2IXvZyocgYLPnG5K0JKw_gwMC9yc=">...</A>@mdocml.bsd.lv + </TD> + <TD> + bug-reports, general questions, and announcements + </TD> + </TR> + <TR> + <TD> + tec<A CLASS="external" TITLE="Reveal this e-mail address" + HREF="http://www.google.com/recaptcha/mailhide/d?k=01qDX_iV0RlUOarEvb6mR28g==&c=gRXsTjza0NNCFPaYu-Taj2tF0pmYZSc90EZkFkhkxgo=">...</A>@mdocml.bsd.lv + </TD> + <TD> + patches and system discussions + </TD> + </TR> + <TR> + <TD> + sou<A CLASS="external" TITLE="Reveal this e-mail address" + HREF="http://www.google.com/recaptcha/mailhide/d?k=01prQrAZhhl2EbIwVcRfABsQ==&c=KtTW4Yic9xk-8g40KzJoca4fR3MYXv28g8NC6OQV-T8=">...</A>@mdocml.bsd.lv + </TD> + <TD> + source commit messages + </TD> + </TR> + </TBODY> + </TABLE> + <H1> + <A NAME="news">News</A> + </H1> + <P CLASS="news"> + 23-03-2011: version 1.12.1 + </P> + <P> + Significant work on <A HREF="apropos.1.html">apropos</A> and <A HREF="mandocdb.8.html">mandocdb</A>. These tools are + now much more robust. + A <A HREF="whatis.1.html">whatis</A> implementation is now handled as an <A HREF="apropos.1.html">apropos</A> mode. + These tools are also able to minimally handle pre-formatted pages, that is, those already formatted by another utility + such as GNU troff. + </P> + <P> + The <A HREF="man.cgi.7.html">man.cgi</A> script is also now available for wider testing. It interfaces with <A + HREF="mandocdb.8.html">mandocdb</A> manuals cached by <A HREF="catman.8.html">catman</A>. HTML output is generated + on-the-fly by <A HREF="mandoc.3.html">libmandoc</A> or internal methods to convert pre-formatted pages. + </P> + <P> + The mailing list archive for the discuss and tech lists are being hosted by <A CLASS="external" + HREF="http://www.gmane.org">Gmane</A> at <A HREF="http://dir.gmane.org/gmane.comp.tools.mdocml.user" + CLASS="external">gmane.comp.tools.mdocml.user</A> and <A HREF="http://dir.gmane.org/gmane.comp.tools.mdocml.devel" + CLASS="external">gmane.comp.tools.mdocml.devel</A>, respectively. + </P> + <P> + Lastly, I'm no longer providing binaries, as nobody has asked for them. + </P> + <P> + See <A HREF="http://mdocml.bsd.lv/cgi-bin/cvsweb/index.sgml?cvsroot=mdocml">cvsweb</A> for + historical notes. + </P> + <P CLASS="foot"> + <SMALL> + Copyright © 2008–2011 + <A CLASS="external" HREF="http://kristaps.bsd.lv">Kristaps Dzonsons</A>, + $Date: 2012/03/24 02:07:32 $ + </SMALL> + </P> + </BODY> +</HTML> diff --git a/lib.c b/lib.c new file mode 100644 index 000000000000..7a18a5dd4fe6 --- /dev/null +++ b/lib.c @@ -0,0 +1,39 @@ +/* $Id: lib.c,v 1.9 2011/03/22 14:33:05 kristaps Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "mdoc.h" +#include "mandoc.h" +#include "libmdoc.h" + +#define LINE(x, y) \ + if (0 == strcmp(p, x)) return(y); + +const char * +mdoc_a2lib(const char *p) +{ + +#include "lib.in" + + return(NULL); +} diff --git a/lib.in b/lib.in new file mode 100644 index 000000000000..230a465ad3dd --- /dev/null +++ b/lib.in @@ -0,0 +1,99 @@ +/* $Id: lib.in,v 1.13 2012/01/28 23:46:28 joerg Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * These are all possible .Lb strings. When a new library is added, add + * its short-string to the left-hand side and formatted string to the + * right-hand side. + * + * Be sure to escape strings. + */ + +LINE("libarchive", "Reading and Writing Streaming Archives Library (libarchive, \\-larchive)") +LINE("libarm", "ARM Architecture Library (libarm, \\-larm)") +LINE("libarm32", "ARM32 Architecture Library (libarm32, \\-larm32)") +LINE("libbluetooth", "Bluetooth Library (libbluetooth, \\-lbluetooth)") +LINE("libbsm", "Basic Security Module User Library (libbsm, \\-lbsm)") +LINE("libc", "Standard C Library (libc, \\-lc)") +LINE("libc_r", "Reentrant C\\~Library (libc_r, \\-lc_r)") +LINE("libcalendar", "Calendar Arithmetic Library (libcalendar, \\-lcalendar)") +LINE("libcam", "Common Access Method User Library (libcam, \\-lcam)") +LINE("libcdk", "Curses Development Kit Library (libcdk, \\-lcdk)") +LINE("libcipher", "FreeSec Crypt Library (libcipher, \\-lcipher)") +LINE("libcompat", "Compatibility Library (libcompat, \\-lcompat)") +LINE("libcrypt", "Crypt Library (libcrypt, \\-lcrypt)") +LINE("libcurses", "Curses Library (libcurses, \\-lcurses)") +LINE("libdevinfo", "Device and Resource Information Utility Library (libdevinfo, \\-ldevinfo)") +LINE("libdevstat", "Device Statistics Library (libdevstat, \\-ldevstat)") +LINE("libdisk", "Interface to Slice and Partition Labels Library (libdisk, \\-ldisk)") +LINE("libdwarf", "DWARF Access Library (libdwarf, \\-ldwarf)") +LINE("libedit", "Command Line Editor Library (libedit, \\-ledit)") +LINE("libelf", "ELF Access Library (libelf, \\-lelf)") +LINE("libevent", "Event Notification Library (libevent, \\-levent)") +LINE("libfetch", "File Transfer Library for URLs (libfetch, \\-lfetch)") +LINE("libform", "Curses Form Library (libform, \\-lform)") +LINE("libgeom", "Userland API Library for kernel GEOM subsystem (libgeom, \\-lgeom)") +LINE("libgpib", "General-Purpose Instrument Bus (GPIB) library (libgpib, \\-lgpib)") +LINE("libi386", "i386 Architecture Library (libi386, \\-li386)") +LINE("libintl", "Internationalized Message Handling Library (libintl, \\-lintl)") +LINE("libipsec", "IPsec Policy Control Library (libipsec, \\-lipsec)") +LINE("libipx", "IPX Address Conversion Support Library (libipx, \\-lipx)") +LINE("libiscsi", "iSCSI protocol library (libiscsi, \\-liscsi)") +LINE("libisns", "Internet Storage Name Service Library (libisns, \\-lisns)") +LINE("libjail", "Jail Library (libjail, \\-ljail)") +LINE("libkiconv", "Kernel side iconv library (libkiconv, \\-lkiconv)") +LINE("libkse", "N:M Threading Library (libkse, \\-lkse)") +LINE("libkvm", "Kernel Data Access Library (libkvm, \\-lkvm)") +LINE("libm", "Math Library (libm, \\-lm)") +LINE("libm68k", "m68k Architecture Library (libm68k, \\-lm68k)") +LINE("libmagic", "Magic Number Recognition Library (libmagic, \\-lmagic)") +LINE("libmd", "Message Digest (MD4, MD5, etc.) Support Library (libmd, \\-lmd)") +LINE("libmemstat", "Kernel Memory Allocator Statistics Library (libmemstat, \\-lmemstat)") +LINE("libmenu", "Curses Menu Library (libmenu, \\-lmenu)") +LINE("libnetgraph", "Netgraph User Library (libnetgraph, \\-lnetgraph)") +LINE("libnetpgp", "Netpgp signing, verification, encryption and decryption (libnetpgp, \\-lnetpgp)") +LINE("libossaudio", "OSS Audio Emulation Library (libossaudio, \\-lossaudio)") +LINE("libpam", "Pluggable Authentication Module Library (libpam, \\-lpam)") +LINE("libpcap", "Capture Library (libpcap, \\-lpcap)") +LINE("libpci", "PCI Bus Access Library (libpci, \\-lpci)") +LINE("libpmc", "Performance Counters Library (libpmc, \\-lpmc)") +LINE("libposix", "POSIX Compatibility Library (libposix, \\-lposix)") +LINE("libppath", "Property-List Paths Library (libppath, \\-lppath)") +LINE("libprop", "Property Container Object Library (libprop, \\-lprop)") +LINE("libpthread", "POSIX Threads Library (libpthread, \\-lpthread)") +LINE("libpuffs", "puffs Convenience Library (libpuffs, \\-lpuffs)") +LINE("libquota", "Disk Quota Access and Control Library (libquota, \\-lquota)") +LINE("librefuse", "File System in Userspace Convenience Library (librefuse, \\-lrefuse)") +LINE("libresolv", "DNS Resolver Library (libresolv, \\-lresolv)") +LINE("librpcsec_gss", "RPC GSS-API Authentication Library (librpcsec_gss, \\-lrpcsec_gss)") +LINE("librpcsvc", "RPC Service Library (librpcsvc, \\-lrpcsvc)") +LINE("librt", "POSIX Real\\-time Library (librt, \\-lrt)") +LINE("libsaslc", "Simple Authentication and Security Layer client library (libsaslc, \\-lsaslc)") +LINE("libsdp", "Bluetooth Service Discovery Protocol User Library (libsdp, \\-lsdp)") +LINE("libssp", "Buffer Overflow Protection Library (libssp, \\-lssp)") +LINE("libSystem", "System Library (libSystem, \\-lSystem)") +LINE("libtermcap", "Termcap Access Library (libtermcap, \\-ltermcap)") +LINE("libterminfo", "Terminal Information Library (libterminfo, \\-lterminfo)") +LINE("libthr", "1:1 Threading Library (libthr, \\-lthr)") +LINE("libufs", "UFS File System Access Library (libufs, \\-lufs)") +LINE("libugidfw", "File System Firewall Interface Library (libugidfw, \\-lugidfw)") +LINE("libulog", "User Login Record Library (libulog, \\-lulog)") +LINE("libusbhid", "USB Human Interface Devices Library (libusbhid, \\-lusbhid)") +LINE("libutil", "System Utilities Library (libutil, \\-lutil)") +LINE("libvgl", "Video Graphics Library (libvgl, \\-lvgl)") +LINE("libx86_64", "x86_64 Architecture Library (libx86_64, \\-lx86_64)") +LINE("libz", "Compression Library (libz, \\-lz)") diff --git a/libman.h b/libman.h new file mode 100644 index 000000000000..4bc5128204f6 --- /dev/null +++ b/libman.h @@ -0,0 +1,85 @@ +/* $Id: libman.h,v 1.55 2011/11/07 01:24:40 schwarze Exp $ */ +/* + * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef LIBMAN_H +#define LIBMAN_H + +enum man_next { + MAN_NEXT_SIBLING = 0, + MAN_NEXT_CHILD +}; + +struct man { + struct mparse *parse; /* parse pointer */ + int flags; /* parse flags */ +#define MAN_HALT (1 << 0) /* badness happened: die */ +#define MAN_ELINE (1 << 1) /* Next-line element scope. */ +#define MAN_BLINE (1 << 2) /* Next-line block scope. */ +#define MAN_ILINE (1 << 3) /* Ignored in next-line scope. */ +#define MAN_LITERAL (1 << 4) /* Literal input. */ +#define MAN_BPLINE (1 << 5) +#define MAN_NEWLINE (1 << 6) /* first macro/text in a line */ + enum man_next next; /* where to put the next node */ + struct man_node *last; /* the last parsed node */ + struct man_node *first; /* the first parsed node */ + struct man_meta meta; /* document meta-data */ + struct roff *roff; +}; + +#define MACRO_PROT_ARGS struct man *m, \ + enum mant tok, \ + int line, \ + int ppos, \ + int *pos, \ + char *buf + +struct man_macro { + int (*fp)(MACRO_PROT_ARGS); + int flags; +#define MAN_SCOPED (1 << 0) +#define MAN_EXPLICIT (1 << 1) /* See blk_imp(). */ +#define MAN_FSCOPED (1 << 2) /* See blk_imp(). */ +#define MAN_NSCOPED (1 << 3) /* See in_line_eoln(). */ +#define MAN_NOCLOSE (1 << 4) /* See blk_exp(). */ +#define MAN_BSCOPE (1 << 5) /* Break BLINE scope. */ +}; + +extern const struct man_macro *const man_macros; + +__BEGIN_DECLS + +#define man_pmsg(m, l, p, t) \ + mandoc_msg((t), (m)->parse, (l), (p), NULL) +#define man_nmsg(m, n, t) \ + mandoc_msg((t), (m)->parse, (n)->line, (n)->pos, NULL) +int man_word_alloc(struct man *, int, int, const char *); +int man_block_alloc(struct man *, int, int, enum mant); +int man_head_alloc(struct man *, int, int, enum mant); +int man_tail_alloc(struct man *, int, int, enum mant); +int man_body_alloc(struct man *, int, int, enum mant); +int man_elem_alloc(struct man *, int, int, enum mant); +void man_node_delete(struct man *, struct man_node *); +void man_hash_init(void); +enum mant man_hash_find(const char *); +int man_macroend(struct man *); +int man_valid_post(struct man *); +int man_valid_pre(struct man *, struct man_node *); +int man_unscope(struct man *, + const struct man_node *, enum mandocerr); + +__END_DECLS + +#endif /*!LIBMAN_H*/ diff --git a/libmandoc.h b/libmandoc.h new file mode 100644 index 000000000000..de422884a214 --- /dev/null +++ b/libmandoc.h @@ -0,0 +1,92 @@ +/* $Id: libmandoc.h,v 1.29 2011/12/02 01:37:14 schwarze Exp $ */ +/* + * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef LIBMANDOC_H +#define LIBMANDOC_H + +enum rofferr { + ROFF_CONT, /* continue processing line */ + ROFF_RERUN, /* re-run roff interpreter with offset */ + ROFF_APPEND, /* re-run main parser, appending next line */ + ROFF_REPARSE, /* re-run main parser on the result */ + ROFF_SO, /* include another file */ + ROFF_IGN, /* ignore current line */ + ROFF_TBL, /* a table row was successfully parsed */ + ROFF_EQN, /* an equation was successfully parsed */ + ROFF_ERR /* badness: puke and stop */ +}; + +enum regs { + REG_nS = 0, /* nS register */ + REG__MAX +}; + +__BEGIN_DECLS + +struct roff; +struct mdoc; +struct man; + +void mandoc_msg(enum mandocerr, struct mparse *, + int, int, const char *); +void mandoc_vmsg(enum mandocerr, struct mparse *, + int, int, const char *, ...); +char *mandoc_getarg(struct mparse *, char **, int, int *); +char *mandoc_normdate(struct mparse *, char *, int, int); +int mandoc_eos(const char *, size_t, int); +int mandoc_getcontrol(const char *, int *); +int mandoc_strntoi(const char *, size_t, int); +const char *mandoc_a2msec(const char*); + +void mdoc_free(struct mdoc *); +struct mdoc *mdoc_alloc(struct roff *, struct mparse *); +void mdoc_reset(struct mdoc *); +int mdoc_parseln(struct mdoc *, int, char *, int); +int mdoc_endparse(struct mdoc *); +int mdoc_addspan(struct mdoc *, const struct tbl_span *); +int mdoc_addeqn(struct mdoc *, const struct eqn *); + +void man_free(struct man *); +struct man *man_alloc(struct roff *, struct mparse *); +void man_reset(struct man *); +int man_parseln(struct man *, int, char *, int); +int man_endparse(struct man *); +int man_addspan(struct man *, const struct tbl_span *); +int man_addeqn(struct man *, const struct eqn *); + +void roff_free(struct roff *); +struct roff *roff_alloc(struct mparse *); +void roff_reset(struct roff *); +enum rofferr roff_parseln(struct roff *, int, + char **, size_t *, int, int *); +void roff_endparse(struct roff *); +int roff_regisset(const struct roff *, enum regs); +unsigned int roff_regget(const struct roff *, enum regs); +void roff_regunset(struct roff *, enum regs); +char *roff_strdup(const struct roff *, const char *); +#if 0 +char roff_eqndelim(const struct roff *); +void roff_openeqn(struct roff *, const char *, + int, int, const char *); +int roff_closeeqn(struct roff *); +#endif + +const struct tbl_span *roff_span(const struct roff *); +const struct eqn *roff_eqn(const struct roff *); + +__END_DECLS + +#endif /*!LIBMANDOC_H*/ diff --git a/libmdoc.h b/libmdoc.h new file mode 100644 index 000000000000..af1729268a44 --- /dev/null +++ b/libmdoc.h @@ -0,0 +1,141 @@ +/* $Id: libmdoc.h,v 1.78 2011/12/02 01:37:14 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef LIBMDOC_H +#define LIBMDOC_H + +enum mdoc_next { + MDOC_NEXT_SIBLING = 0, + MDOC_NEXT_CHILD +}; + +struct mdoc { + struct mparse *parse; /* parse pointer */ + int flags; /* parse flags */ +#define MDOC_HALT (1 << 0) /* error in parse: halt */ +#define MDOC_LITERAL (1 << 1) /* in a literal scope */ +#define MDOC_PBODY (1 << 2) /* in the document body */ +#define MDOC_NEWLINE (1 << 3) /* first macro/text in a line */ +#define MDOC_PHRASELIT (1 << 4) /* literal within a partila phrase */ +#define MDOC_PPHRASE (1 << 5) /* within a partial phrase */ +#define MDOC_FREECOL (1 << 6) /* `It' invocation should close */ +#define MDOC_SYNOPSIS (1 << 7) /* SYNOPSIS-style formatting */ + enum mdoc_next next; /* where to put the next node */ + struct mdoc_node *last; /* the last node parsed */ + struct mdoc_node *first; /* the first node parsed */ + struct mdoc_meta meta; /* document meta-data */ + enum mdoc_sec lastnamed; + enum mdoc_sec lastsec; + struct roff *roff; +}; + +#define MACRO_PROT_ARGS struct mdoc *m, \ + enum mdoct tok, \ + int line, \ + int ppos, \ + int *pos, \ + char *buf + +struct mdoc_macro { + int (*fp)(MACRO_PROT_ARGS); + int flags; +#define MDOC_CALLABLE (1 << 0) +#define MDOC_PARSED (1 << 1) +#define MDOC_EXPLICIT (1 << 2) +#define MDOC_PROLOGUE (1 << 3) +#define MDOC_IGNDELIM (1 << 4) + /* Reserved words in arguments treated as text. */ +}; + +enum margserr { + ARGS_ERROR, + ARGS_EOLN, /* end-of-line */ + ARGS_WORD, /* normal word */ + ARGS_PUNCT, /* series of punctuation */ + ARGS_QWORD, /* quoted word */ + ARGS_PHRASE, /* Ta'd phrase (-column) */ + ARGS_PPHRASE, /* tabbed phrase (-column) */ + ARGS_PEND /* last phrase (-column) */ +}; + +enum margverr { + ARGV_ERROR, + ARGV_EOLN, /* end of line */ + ARGV_ARG, /* valid argument */ + ARGV_WORD /* normal word (or bad argument---same thing) */ +}; + +/* + * A punctuation delimiter is opening, closing, or "middle mark" + * punctuation. These govern spacing. + * Opening punctuation (e.g., the opening parenthesis) suppresses the + * following space; closing punctuation (e.g., the closing parenthesis) + * suppresses the leading space; middle punctuation (e.g., the vertical + * bar) can do either. The middle punctuation delimiter bends the rules + * depending on usage. + */ +enum mdelim { + DELIM_NONE = 0, + DELIM_OPEN, + DELIM_MIDDLE, + DELIM_CLOSE, + DELIM_MAX +}; + +extern const struct mdoc_macro *const mdoc_macros; + +__BEGIN_DECLS + +#define mdoc_pmsg(m, l, p, t) \ + mandoc_msg((t), (m)->parse, (l), (p), NULL) +#define mdoc_nmsg(m, n, t) \ + mandoc_msg((t), (m)->parse, (n)->line, (n)->pos, NULL) +int mdoc_macro(MACRO_PROT_ARGS); +int mdoc_word_alloc(struct mdoc *, + int, int, const char *); +int mdoc_elem_alloc(struct mdoc *, int, int, + enum mdoct, struct mdoc_arg *); +int mdoc_block_alloc(struct mdoc *, int, int, + enum mdoct, struct mdoc_arg *); +int mdoc_head_alloc(struct mdoc *, int, int, enum mdoct); +int mdoc_tail_alloc(struct mdoc *, int, int, enum mdoct); +int mdoc_body_alloc(struct mdoc *, int, int, enum mdoct); +int mdoc_endbody_alloc(struct mdoc *m, int line, int pos, + enum mdoct tok, struct mdoc_node *body, + enum mdoc_endbody end); +void mdoc_node_delete(struct mdoc *, struct mdoc_node *); +void mdoc_hash_init(void); +enum mdoct mdoc_hash_find(const char *); +const char *mdoc_a2att(const char *); +const char *mdoc_a2lib(const char *); +const char *mdoc_a2st(const char *); +const char *mdoc_a2arch(const char *); +const char *mdoc_a2vol(const char *); +int mdoc_valid_pre(struct mdoc *, struct mdoc_node *); +int mdoc_valid_post(struct mdoc *); +enum margverr mdoc_argv(struct mdoc *, int, enum mdoct, + struct mdoc_arg **, int *, char *); +void mdoc_argv_free(struct mdoc_arg *); +enum margserr mdoc_args(struct mdoc *, int, + int *, char *, enum mdoct, char **); +enum margserr mdoc_zargs(struct mdoc *, int, + int *, char *, char **); +int mdoc_macroend(struct mdoc *); +enum mdelim mdoc_isdelim(const char *); + +__END_DECLS + +#endif /*!LIBMDOC_H*/ diff --git a/libroff.h b/libroff.h new file mode 100644 index 000000000000..0bdd5a360478 --- /dev/null +++ b/libroff.h @@ -0,0 +1,84 @@ +/* $Id: libroff.h,v 1.27 2011/07/25 15:37:00 kristaps Exp $ */ +/* + * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef LIBROFF_H +#define LIBROFF_H + +__BEGIN_DECLS + +enum tbl_part { + TBL_PART_OPTS, /* in options (first line) */ + TBL_PART_LAYOUT, /* describing layout */ + TBL_PART_DATA, /* creating data rows */ + TBL_PART_CDATA /* continue previous row */ +}; + +struct tbl_node { + struct mparse *parse; /* parse point */ + int pos; /* invocation column */ + int line; /* invocation line */ + enum tbl_part part; + struct tbl opts; + struct tbl_row *first_row; + struct tbl_row *last_row; + struct tbl_span *first_span; + struct tbl_span *current_span; + struct tbl_span *last_span; + struct tbl_head *first_head; + struct tbl_head *last_head; + struct tbl_node *next; +}; + +struct eqn_node { + struct eqn_def *defs; + size_t defsz; + char *data; + size_t rew; + size_t cur; + size_t sz; + int gsize; + struct eqn eqn; + struct mparse *parse; + struct eqn_node *next; +}; + +struct eqn_def { + char *key; + size_t keysz; + char *val; + size_t valsz; +}; + +struct tbl_node *tbl_alloc(int, int, struct mparse *); +void tbl_restart(int, int, struct tbl_node *); +void tbl_free(struct tbl_node *); +void tbl_reset(struct tbl_node *); +enum rofferr tbl_read(struct tbl_node *, int, const char *, int); +int tbl_option(struct tbl_node *, int, const char *); +int tbl_layout(struct tbl_node *, int, const char *); +int tbl_data(struct tbl_node *, int, const char *); +int tbl_cdata(struct tbl_node *, int, const char *); +const struct tbl_span *tbl_span(struct tbl_node *); +void tbl_end(struct tbl_node **); +struct eqn_node *eqn_alloc(const char *, int, int, struct mparse *); +enum rofferr eqn_end(struct eqn_node **); +void eqn_free(struct eqn_node *); +enum rofferr eqn_read(struct eqn_node **, int, + const char *, int, int *); + +__END_DECLS + +#endif /*LIBROFF_H*/ diff --git a/main.c b/main.c new file mode 100644 index 000000000000..fec83fba513a --- /dev/null +++ b/main.c @@ -0,0 +1,401 @@ +/* $Id: main.c,v 1.165 2011/10/06 22:29:12 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * Copyright (c) 2010, 2011 Ingo Schwarze <schwarze@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <assert.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "mandoc.h" +#include "main.h" +#include "mdoc.h" +#include "man.h" + +#if !defined(__GNUC__) || (__GNUC__ < 2) +# if !defined(lint) +# define __attribute__(x) +# endif +#endif /* !defined(__GNUC__) || (__GNUC__ < 2) */ + +typedef void (*out_mdoc)(void *, const struct mdoc *); +typedef void (*out_man)(void *, const struct man *); +typedef void (*out_free)(void *); + +enum outt { + OUTT_ASCII = 0, /* -Tascii */ + OUTT_LOCALE, /* -Tlocale */ + OUTT_UTF8, /* -Tutf8 */ + OUTT_TREE, /* -Ttree */ + OUTT_MAN, /* -Tman */ + OUTT_HTML, /* -Thtml */ + OUTT_XHTML, /* -Txhtml */ + OUTT_LINT, /* -Tlint */ + OUTT_PS, /* -Tps */ + OUTT_PDF /* -Tpdf */ +}; + +struct curparse { + struct mparse *mp; + enum mandoclevel wlevel; /* ignore messages below this */ + int wstop; /* stop after a file with a warning */ + enum outt outtype; /* which output to use */ + out_mdoc outmdoc; /* mdoc output ptr */ + out_man outman; /* man output ptr */ + out_free outfree; /* free output ptr */ + void *outdata; /* data for output */ + char outopts[BUFSIZ]; /* buf of output opts */ +}; + +static int moptions(enum mparset *, char *); +static void mmsg(enum mandocerr, enum mandoclevel, + const char *, int, int, const char *); +static void parse(struct curparse *, int, + const char *, enum mandoclevel *); +static int toptions(struct curparse *, char *); +static void usage(void) __attribute__((noreturn)); +static void version(void) __attribute__((noreturn)); +static int woptions(struct curparse *, char *); + +static const char *progname; + +int +main(int argc, char *argv[]) +{ + int c; + struct curparse curp; + enum mparset type; + enum mandoclevel rc; + + progname = strrchr(argv[0], '/'); + if (progname == NULL) + progname = argv[0]; + else + ++progname; + + memset(&curp, 0, sizeof(struct curparse)); + + type = MPARSE_AUTO; + curp.outtype = OUTT_ASCII; + curp.wlevel = MANDOCLEVEL_FATAL; + + /* LINTED */ + while (-1 != (c = getopt(argc, argv, "m:O:T:VW:"))) + switch (c) { + case ('m'): + if ( ! moptions(&type, optarg)) + return((int)MANDOCLEVEL_BADARG); + break; + case ('O'): + (void)strlcat(curp.outopts, optarg, BUFSIZ); + (void)strlcat(curp.outopts, ",", BUFSIZ); + break; + case ('T'): + if ( ! toptions(&curp, optarg)) + return((int)MANDOCLEVEL_BADARG); + break; + case ('W'): + if ( ! woptions(&curp, optarg)) + return((int)MANDOCLEVEL_BADARG); + break; + case ('V'): + version(); + /* NOTREACHED */ + default: + usage(); + /* NOTREACHED */ + } + + curp.mp = mparse_alloc(type, curp.wlevel, mmsg, &curp); + + /* + * Conditionally start up the lookaside buffer before parsing. + */ + if (OUTT_MAN == curp.outtype) + mparse_keep(curp.mp); + + argc -= optind; + argv += optind; + + rc = MANDOCLEVEL_OK; + + if (NULL == *argv) + parse(&curp, STDIN_FILENO, "<stdin>", &rc); + + while (*argv) { + parse(&curp, -1, *argv, &rc); + if (MANDOCLEVEL_OK != rc && curp.wstop) + break; + ++argv; + } + + if (curp.outfree) + (*curp.outfree)(curp.outdata); + if (curp.mp) + mparse_free(curp.mp); + + return((int)rc); +} + +static void +version(void) +{ + + printf("%s %s\n", progname, VERSION); + exit((int)MANDOCLEVEL_OK); +} + +static void +usage(void) +{ + + fprintf(stderr, "usage: %s " + "[-V] " + "[-foption] " + "[-mformat] " + "[-Ooption] " + "[-Toutput] " + "[-Wlevel] " + "[file...]\n", + progname); + + exit((int)MANDOCLEVEL_BADARG); +} + +static void +parse(struct curparse *curp, int fd, + const char *file, enum mandoclevel *level) +{ + enum mandoclevel rc; + struct mdoc *mdoc; + struct man *man; + + /* Begin by parsing the file itself. */ + + assert(file); + assert(fd >= -1); + + rc = mparse_readfd(curp->mp, fd, file); + + /* Stop immediately if the parse has failed. */ + + if (MANDOCLEVEL_FATAL <= rc) + goto cleanup; + + /* + * With -Wstop and warnings or errors of at least the requested + * level, do not produce output. + */ + + if (MANDOCLEVEL_OK != rc && curp->wstop) + goto cleanup; + + /* If unset, allocate output dev now (if applicable). */ + + if ( ! (curp->outman && curp->outmdoc)) { + switch (curp->outtype) { + case (OUTT_XHTML): + curp->outdata = xhtml_alloc(curp->outopts); + curp->outfree = html_free; + break; + case (OUTT_HTML): + curp->outdata = html_alloc(curp->outopts); + curp->outfree = html_free; + break; + case (OUTT_UTF8): + curp->outdata = utf8_alloc(curp->outopts); + curp->outfree = ascii_free; + break; + case (OUTT_LOCALE): + curp->outdata = locale_alloc(curp->outopts); + curp->outfree = ascii_free; + break; + case (OUTT_ASCII): + curp->outdata = ascii_alloc(curp->outopts); + curp->outfree = ascii_free; + break; + case (OUTT_PDF): + curp->outdata = pdf_alloc(curp->outopts); + curp->outfree = pspdf_free; + break; + case (OUTT_PS): + curp->outdata = ps_alloc(curp->outopts); + curp->outfree = pspdf_free; + break; + default: + break; + } + + switch (curp->outtype) { + case (OUTT_HTML): + /* FALLTHROUGH */ + case (OUTT_XHTML): + curp->outman = html_man; + curp->outmdoc = html_mdoc; + break; + case (OUTT_TREE): + curp->outman = tree_man; + curp->outmdoc = tree_mdoc; + break; + case (OUTT_MAN): + curp->outmdoc = man_mdoc; + curp->outman = man_man; + break; + case (OUTT_PDF): + /* FALLTHROUGH */ + case (OUTT_ASCII): + /* FALLTHROUGH */ + case (OUTT_UTF8): + /* FALLTHROUGH */ + case (OUTT_LOCALE): + /* FALLTHROUGH */ + case (OUTT_PS): + curp->outman = terminal_man; + curp->outmdoc = terminal_mdoc; + break; + default: + break; + } + } + + mparse_result(curp->mp, &mdoc, &man); + + /* Execute the out device, if it exists. */ + + if (man && curp->outman) + (*curp->outman)(curp->outdata, man); + if (mdoc && curp->outmdoc) + (*curp->outmdoc)(curp->outdata, mdoc); + + cleanup: + + mparse_reset(curp->mp); + + if (*level < rc) + *level = rc; +} + +static int +moptions(enum mparset *tflags, char *arg) +{ + + if (0 == strcmp(arg, "doc")) + *tflags = MPARSE_MDOC; + else if (0 == strcmp(arg, "andoc")) + *tflags = MPARSE_AUTO; + else if (0 == strcmp(arg, "an")) + *tflags = MPARSE_MAN; + else { + fprintf(stderr, "%s: Bad argument\n", arg); + return(0); + } + + return(1); +} + +static int +toptions(struct curparse *curp, char *arg) +{ + + if (0 == strcmp(arg, "ascii")) + curp->outtype = OUTT_ASCII; + else if (0 == strcmp(arg, "lint")) { + curp->outtype = OUTT_LINT; + curp->wlevel = MANDOCLEVEL_WARNING; + } else if (0 == strcmp(arg, "tree")) + curp->outtype = OUTT_TREE; + else if (0 == strcmp(arg, "man")) + curp->outtype = OUTT_MAN; + else if (0 == strcmp(arg, "html")) + curp->outtype = OUTT_HTML; + else if (0 == strcmp(arg, "utf8")) + curp->outtype = OUTT_UTF8; + else if (0 == strcmp(arg, "locale")) + curp->outtype = OUTT_LOCALE; + else if (0 == strcmp(arg, "xhtml")) + curp->outtype = OUTT_XHTML; + else if (0 == strcmp(arg, "ps")) + curp->outtype = OUTT_PS; + else if (0 == strcmp(arg, "pdf")) + curp->outtype = OUTT_PDF; + else { + fprintf(stderr, "%s: Bad argument\n", arg); + return(0); + } + + return(1); +} + +static int +woptions(struct curparse *curp, char *arg) +{ + char *v, *o; + const char *toks[6]; + + toks[0] = "stop"; + toks[1] = "all"; + toks[2] = "warning"; + toks[3] = "error"; + toks[4] = "fatal"; + toks[5] = NULL; + + while (*arg) { + o = arg; + switch (getsubopt(&arg, UNCONST(toks), &v)) { + case (0): + curp->wstop = 1; + break; + case (1): + /* FALLTHROUGH */ + case (2): + curp->wlevel = MANDOCLEVEL_WARNING; + break; + case (3): + curp->wlevel = MANDOCLEVEL_ERROR; + break; + case (4): + curp->wlevel = MANDOCLEVEL_FATAL; + break; + default: + fprintf(stderr, "-W%s: Bad argument\n", o); + return(0); + } + } + + return(1); +} + +static void +mmsg(enum mandocerr t, enum mandoclevel lvl, + const char *file, int line, int col, const char *msg) +{ + + fprintf(stderr, "%s:%d:%d: %s: %s", + file, line, col + 1, + mparse_strlevel(lvl), + mparse_strerror(t)); + + if (msg) + fprintf(stderr, ": %s", msg); + + fputc('\n', stderr); +} diff --git a/main.h b/main.h new file mode 100644 index 000000000000..79dcf489ae65 --- /dev/null +++ b/main.h @@ -0,0 +1,61 @@ +/* $Id: main.h,v 1.15 2011/10/06 22:29:12 kristaps Exp $ */ +/* + * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef MAIN_H +#define MAIN_H + +__BEGIN_DECLS + +struct mdoc; +struct man; + +#define UNCONST(a) ((void *)(uintptr_t)(const void *)(a)) + + +/* + * Definitions for main.c-visible output device functions, e.g., -Thtml + * and -Tascii. Note that ascii_alloc() is named as such in + * anticipation of latin1_alloc() and so on, all of which map into the + * terminal output routines with different character settings. + */ + +void *html_alloc(char *); +void *xhtml_alloc(char *); +void html_mdoc(void *, const struct mdoc *); +void html_man(void *, const struct man *); +void html_free(void *); + +void tree_mdoc(void *, const struct mdoc *); +void tree_man(void *, const struct man *); + +void man_mdoc(void *, const struct mdoc *); +void man_man(void *, const struct man *); + +void *locale_alloc(char *); +void *utf8_alloc(char *); +void *ascii_alloc(char *); +void ascii_free(void *); + +void *pdf_alloc(char *); +void *ps_alloc(char *); +void pspdf_free(void *); + +void terminal_mdoc(void *, const struct mdoc *); +void terminal_man(void *, const struct man *); + +__END_DECLS + +#endif /*!MAIN_H*/ diff --git a/man-cgi.css b/man-cgi.css new file mode 100644 index 000000000000..5300267cfbc1 --- /dev/null +++ b/man-cgi.css @@ -0,0 +1,13 @@ +body { font-family: Helvetica, Arial, sans-serif; } +body > div { padding-left: 2em; + padding-top: 1em; } +body > div#mancgi { padding-left: 0em; + padding-top: 0em; } +body > div.results { font-size: smaller; } +#mancgi fieldset { text-align: center; + border: thin solid silver; + border-radius: 1em; + font-size: small; } +#mancgi input[name=expr] { width: 25%; } +.results td.title { vertical-align: top; + padding-right: 1em; } diff --git a/man.7 b/man.7 new file mode 100644 index 000000000000..1715a7ca119b --- /dev/null +++ b/man.7 @@ -0,0 +1,913 @@ +.\" $Id: man.7,v 1.113 2012/01/03 15:16:24 kristaps Exp $ +.\" +.\" Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> +.\" Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: January 3 2012 $ +.Dt MAN 7 +.Os +.Sh NAME +.Nm man +.Nd legacy formatting language for manual pages +.Sh DESCRIPTION +Traditionally, the +.Nm man +language has been used to write +.Ux +manuals for the +.Xr man 1 +utility. +It supports limited control of presentational details like fonts, +indentation and spacing. +This reference document describes the structure of manual pages +and the syntax and usage of the man language. +.Pp +.Bf -emphasis +Do not use +.Nm +to write your manuals: +.Ef +It lacks support for semantic markup. +Use the +.Xr mdoc 7 +language, instead. +.Pp +In a +.Nm +document, lines beginning with the control character +.Sq \&. +are called +.Dq macro lines . +The first word is the macro name. +It usually consists of two capital letters. +For a list of available macros, see +.Sx MACRO OVERVIEW . +The words following the macro name are arguments to the macro. +.Pp +Lines not beginning with the control character are called +.Dq text lines . +They provide free-form text to be printed; the formatting of the text +depends on the respective processing context: +.Bd -literal -offset indent +\&.SH Macro lines change control state. +Text lines are interpreted within the current state. +.Ed +.Pp +Many aspects of the basic syntax of the +.Nm +language are based on the +.Xr roff 7 +language; see the +.Em LANGUAGE SYNTAX +and +.Em MACRO SYNTAX +sections in the +.Xr roff 7 +manual for details, in particular regarding +comments, escape sequences, whitespace, and quoting. +.Sh MANUAL STRUCTURE +Each +.Nm +document must contain the +.Sx \&TH +macro describing the document's section and title. +It may occur anywhere in the document, although conventionally it +appears as the first macro. +.Pp +Beyond +.Sx \&TH , +at least one macro or text line must appear in the document. +.Pp +The following is a well-formed skeleton +.Nm +file for a utility +.Qq progname : +.Bd -literal -offset indent +\&.TH PROGNAME 1 2009-10-10 +\&.SH NAME +\efBprogname\efR \e(en a description goes here +\&.\e\(dq .SH LIBRARY +\&.\e\(dq For sections 2 & 3 only. +\&.\e\(dq Not used in OpenBSD. +\&.SH SYNOPSIS +\efBprogname\efR [\efB\e-options\efR] arguments... +\&.SH DESCRIPTION +The \efBfoo\efR utility processes files... +\&.\e\(dq .SH IMPLEMENTATION NOTES +\&.\e\(dq Not used in OpenBSD. +\&.\e\(dq .SH RETURN VALUES +\&.\e\(dq For sections 2, 3, & 9 only. +\&.\e\(dq .SH ENVIRONMENT +\&.\e\(dq For sections 1, 6, 7, & 8 only. +\&.\e\(dq .SH FILES +\&.\e\(dq .SH EXIT STATUS +\&.\e\(dq For sections 1, 6, & 8 only. +\&.\e\(dq .SH EXAMPLES +\&.\e\(dq .SH DIAGNOSTICS +\&.\e\(dq For sections 1, 4, 6, 7, & 8 only. +\&.\e\(dq .SH ERRORS +\&.\e\(dq For sections 2, 3, & 9 only. +\&.\e\(dq .SH SEE ALSO +\&.\e\(dq .BR foo ( 1 ) +\&.\e\(dq .SH STANDARDS +\&.\e\(dq .SH HISTORY +\&.\e\(dq .SH AUTHORS +\&.\e\(dq .SH CAVEATS +\&.\e\(dq .SH BUGS +\&.\e\(dq .SH SECURITY CONSIDERATIONS +\&.\e\(dq Not used in OpenBSD. +.Ed +.Pp +The sections in a +.Nm +document are conventionally ordered as they appear above. +Sections should be composed as follows: +.Bl -ohang -offset indent +.It Em NAME +The name(s) and a short description of the documented material. +The syntax for this is generally as follows: +.Pp +.D1 \efBname\efR \e(en description +.It Em LIBRARY +The name of the library containing the documented material, which is +assumed to be a function in a section 2 or 3 manual. +For functions in the C library, this may be as follows: +.Pp +.D1 Standard C Library (libc, -lc) +.It Em SYNOPSIS +Documents the utility invocation syntax, function call syntax, or device +configuration. +.Pp +For the first, utilities (sections 1, 6, and 8), this is +generally structured as follows: +.Pp +.D1 \efBname\efR [-\efBab\efR] [-\efBc\efR\efIarg\efR] \efBpath\efR... +.Pp +For the second, function calls (sections 2, 3, 9): +.Pp +.D1 \&.B char *name(char *\efIarg\efR); +.Pp +And for the third, configurations (section 4): +.Pp +.D1 \&.B name* at cardbus ? function ? +.Pp +Manuals not in these sections generally don't need a +.Em SYNOPSIS . +.It Em DESCRIPTION +This expands upon the brief, one-line description in +.Em NAME . +It usually contains a break-down of the options (if documenting a +command). +.It Em IMPLEMENTATION NOTES +Implementation-specific notes should be kept here. +This is useful when implementing standard functions that may have side +effects or notable algorithmic implications. +.It Em RETURN VALUES +This section documents the return values of functions in sections 2, 3, and 9. +.It Em ENVIRONMENT +Documents any usages of environment variables, e.g., +.Xr environ 7 . +.It Em FILES +Documents files used. +It's helpful to document both the file name and a short description of how +the file is used (created, modified, etc.). +.It Em EXIT STATUS +This section documents the command exit status for +section 1, 6, and 8 utilities. +Historically, this information was described in +.Em DIAGNOSTICS , +a practise that is now discouraged. +.It Em EXAMPLES +Example usages. +This often contains snippets of well-formed, +well-tested invocations. +Make sure that examples work properly! +.It Em DIAGNOSTICS +Documents error conditions. +This is most useful in section 4 manuals. +Historically, this section was used in place of +.Em EXIT STATUS +for manuals in sections 1, 6, and 8; however, this practise is +discouraged. +.It Em ERRORS +Documents error handling in sections 2, 3, and 9. +.It Em SEE ALSO +References other manuals with related topics. +This section should exist for most manuals. +.Pp +.D1 \&.BR bar \&( 1 \&), +.Pp +Cross-references should conventionally be ordered +first by section, then alphabetically. +.It Em STANDARDS +References any standards implemented or used, such as +.Pp +.D1 IEEE Std 1003.2 (\e(lqPOSIX.2\e(rq) +.Pp +If not adhering to any standards, the +.Em HISTORY +section should be used. +.It Em HISTORY +A brief history of the subject, including where support first appeared. +.It Em AUTHORS +Credits to the person or persons who wrote the code and/or documentation. +Authors should generally be noted by both name and email address. +.It Em CAVEATS +Common misuses and misunderstandings should be explained +in this section. +.It Em BUGS +Known bugs, limitations, and work-arounds should be described +in this section. +.It Em SECURITY CONSIDERATIONS +Documents any security precautions that operators should consider. +.El +.Sh MACRO OVERVIEW +This overview is sorted such that macros of similar purpose are listed +together, to help find the best macro for any given purpose. +Deprecated macros are not included in the overview, but can be found +in the alphabetical reference below. +.Ss Page header and footer meta-data +.Bl -column "PP, LP, P" description +.It Sx TH Ta set the title: Ar title section date Op Ar source Op Ar volume +.It Sx AT Ta display AT&T UNIX version in the page footer (<= 1 argument) +.It Sx UC Ta display BSD version in the page footer (<= 1 argument) +.El +.Ss Sections and paragraphs +.Bl -column "PP, LP, P" description +.It Sx SH Ta section header (one line) +.It Sx SS Ta subsection header (one line) +.It Sx PP , LP , P Ta start an undecorated paragraph (no arguments) +.It Sx RS , RE Ta reset the left margin: Op Ar width +.It Sx IP Ta indented paragraph: Op Ar head Op Ar width +.It Sx TP Ta tagged paragraph: Op Ar width +.It Sx HP Ta hanged paragraph: Op Ar width +.It Sx \&br Ta force output line break in text mode (no arguments) +.It Sx \&sp Ta force vertical space: Op Ar height +.It Sx fi , nf Ta fill mode and no-fill mode (no arguments) +.It Sx in Ta additional indent: Op Ar width +.El +.Ss Physical markup +.Bl -column "PP, LP, P" description +.It Sx B Ta boldface font +.It Sx I Ta italic font +.It Sx R Ta roman (default) font +.It Sx SB Ta small boldface font +.It Sx SM Ta small roman font +.It Sx BI Ta alternate between boldface and italic fonts +.It Sx BR Ta alternate between boldface and roman fonts +.It Sx IB Ta alternate between italic and boldface fonts +.It Sx IR Ta alternate between italic and roman fonts +.It Sx RB Ta alternate between roman and boldface fonts +.It Sx RI Ta alternate between roman and italic fonts +.El +.Ss Semantic markup +.Bl -column "PP, LP, P" description +.It Sx OP Ta optional arguments +.El +.Sh MACRO REFERENCE +This section is a canonical reference to all macros, arranged +alphabetically. +For the scoping of individual macros, see +.Sx MACRO SYNTAX . +.Ss \&AT +Sets the volume for the footer for compatibility with man pages from +.Tn AT&T UNIX +releases. +The optional arguments specify which release it is from. +.Ss \&B +Text is rendered in bold face. +.Pp +See also +.Sx \&I +and +.Sx \&R . +.Ss \&BI +Text is rendered alternately in bold face and italic. +Thus, +.Sq .BI this word and that +causes +.Sq this +and +.Sq and +to render in bold face, while +.Sq word +and +.Sq that +render in italics. +Whitespace between arguments is omitted in output. +.Pp +Examples: +.Pp +.Dl \&.BI bold italic bold italic +.Pp +The output of this example will be emboldened +.Dq bold +and italicised +.Dq italic , +with spaces stripped between arguments. +.Pp +See also +.Sx \&IB , +.Sx \&BR , +.Sx \&RB , +.Sx \&RI , +and +.Sx \&IR . +.Ss \&BR +Text is rendered alternately in bold face and roman (the default font). +Whitespace between arguments is omitted in output. +.Pp +See +.Sx \&BI +for an equivalent example. +.Pp +See also +.Sx \&BI , +.Sx \&IB , +.Sx \&RB , +.Sx \&RI , +and +.Sx \&IR . +.Ss \&DT +Has no effect. +Included for compatibility. +.Ss \&HP +Begin a paragraph whose initial output line is left-justified, but +subsequent output lines are indented, with the following syntax: +.Bd -filled -offset indent +.Pf \. Sx \&HP +.Op Cm width +.Ed +.Pp +The +.Cm width +argument must conform to +.Sx Scaling Widths . +If specified, it's saved for later paragraph left-margins; if unspecified, the +saved or default width is used. +.Pp +See also +.Sx \&IP , +.Sx \&LP , +.Sx \&P , +.Sx \&PP , +and +.Sx \&TP . +.Ss \&I +Text is rendered in italics. +.Pp +See also +.Sx \&B +and +.Sx \&R . +.Ss \&IB +Text is rendered alternately in italics and bold face. +Whitespace between arguments is omitted in output. +.Pp +See +.Sx \&BI +for an equivalent example. +.Pp +See also +.Sx \&BI , +.Sx \&BR , +.Sx \&RB , +.Sx \&RI , +and +.Sx \&IR . +.Ss \&IP +Begin an indented paragraph with the following syntax: +.Bd -filled -offset indent +.Pf \. Sx \&IP +.Op Cm head Op Cm width +.Ed +.Pp +The +.Cm width +argument defines the width of the left margin and is defined by +.Sx Scaling Widths . +It's saved for later paragraph left-margins; if unspecified, the saved or +default width is used. +.Pp +The +.Cm head +argument is used as a leading term, flushed to the left margin. +This is useful for bulleted paragraphs and so on. +.Pp +See also +.Sx \&HP , +.Sx \&LP , +.Sx \&P , +.Sx \&PP , +and +.Sx \&TP . +.Ss \&IR +Text is rendered alternately in italics and roman (the default font). +Whitespace between arguments is omitted in output. +.Pp +See +.Sx \&BI +for an equivalent example. +.Pp +See also +.Sx \&BI , +.Sx \&IB , +.Sx \&BR , +.Sx \&RB , +and +.Sx \&RI . +.Ss \&LP +Begin an undecorated paragraph. +The scope of a paragraph is closed by a subsequent paragraph, +sub-section, section, or end of file. +The saved paragraph left-margin width is reset to the default. +.Pp +See also +.Sx \&HP , +.Sx \&IP , +.Sx \&P , +.Sx \&PP , +and +.Sx \&TP . +.Ss \&OP +Optional command-line argument. +This has the following syntax: +.Bd -filled -offset indent +.Pf \. Sx \&OP +.Cm key Op Cm value +.Ed +.Pp +The +.Cm key +is usually a command-line flag and +.Cm value +its argument. +.Ss \&P +Synonym for +.Sx \&LP . +.Pp +See also +.Sx \&HP , +.Sx \&IP , +.Sx \&LP , +.Sx \&PP , +and +.Sx \&TP . +.Ss \&PP +Synonym for +.Sx \&LP . +.Pp +See also +.Sx \&HP , +.Sx \&IP , +.Sx \&LP , +.Sx \&P , +and +.Sx \&TP . +.Ss \&R +Text is rendered in roman (the default font). +.Pp +See also +.Sx \&I +and +.Sx \&B . +.Ss \&RB +Text is rendered alternately in roman (the default font) and bold face. +Whitespace between arguments is omitted in output. +.Pp +See +.Sx \&BI +for an equivalent example. +.Pp +See also +.Sx \&BI , +.Sx \&IB , +.Sx \&BR , +.Sx \&RI , +and +.Sx \&IR . +.Ss \&RE +Explicitly close out the scope of a prior +.Sx \&RS . +The default left margin is restored to the state of the original +.Sx \&RS +invocation. +.Ss \&RI +Text is rendered alternately in roman (the default font) and italics. +Whitespace between arguments is omitted in output. +.Pp +See +.Sx \&BI +for an equivalent example. +.Pp +See also +.Sx \&BI , +.Sx \&IB , +.Sx \&BR , +.Sx \&RB , +and +.Sx \&IR . +.Ss \&RS +Temporarily reset the default left margin. +This has the following syntax: +.Bd -filled -offset indent +.Pf \. Sx \&RS +.Op Cm width +.Ed +.Pp +The +.Cm width +argument must conform to +.Sx Scaling Widths . +If not specified, the saved or default width is used. +.Pp +See also +.Sx \&RE . +.Ss \&SB +Text is rendered in small size (one point smaller than the default font) +bold face. +.Ss \&SH +Begin a section. +The scope of a section is only closed by another section or the end of +file. +The paragraph left-margin width is reset to the default. +.Ss \&SM +Text is rendered in small size (one point smaller than the default +font). +.Ss \&SS +Begin a sub-section. +The scope of a sub-section is closed by a subsequent sub-section, +section, or end of file. +The paragraph left-margin width is reset to the default. +.Ss \&TH +Sets the title of the manual page with the following syntax: +.Bd -filled -offset indent +.Pf \. Sx \&TH +.Ar title section date +.Op Ar source Op Ar volume +.Ed +.Pp +Conventionally, the document +.Ar title +is given in all caps. +The recommended +.Ar date +format is +.Sy YYYY-MM-DD +as specified in the ISO-8601 standard; +if the argument does not conform, it is printed verbatim. +If the +.Ar date +is empty or not specified, the current date is used. +The optional +.Ar source +string specifies the organisation providing the utility. +The +.Ar volume +string replaces the default rendered volume, which is dictated by the +manual section. +.Pp +Examples: +.Pp +.Dl \&.TH CVS 5 "1992-02-12" GNU +.Ss \&TP +Begin a paragraph where the head, if exceeding the indentation width, is +followed by a newline; if not, the body follows on the same line after a +buffer to the indentation width. +Subsequent output lines are indented. +The syntax is as follows: +.Bd -filled -offset indent +.Pf \. Sx \&TP +.Op Cm width +.Ed +.Pp +The +.Cm width +argument must conform to +.Sx Scaling Widths . +If specified, it's saved for later paragraph left-margins; if +unspecified, the saved or default width is used. +.Pp +See also +.Sx \&HP , +.Sx \&IP , +.Sx \&LP , +.Sx \&P , +and +.Sx \&PP . +.Ss \&UC +Sets the volume for the footer for compatibility with man pages from +BSD releases. +The optional first argument specifies which release it is from. +.Ss \&br +Breaks the current line. +Consecutive invocations have no further effect. +.Pp +See also +.Sx \&sp . +.Ss \&fi +End literal mode begun by +.Sx \&nf . +.Ss \&ft +Change the current font mode. +See +.Sx Text Decoration +for a listing of available font modes. +.Ss \&in +Indent relative to the current indentation: +.Pp +.D1 Pf \. Sx \&in Op Cm width +.Pp +If +.Cm width +is signed, the new offset is relative. +Otherwise, it is absolute. +This value is reset upon the next paragraph, section, or sub-section. +.Ss \&na +Don't align to the right margin. +.Ss \&nf +Begin literal mode: all subsequent free-form lines have their end of +line boundaries preserved. +May be ended by +.Sx \&fi . +Literal mode is implicitly ended by +.Sx \&SH +or +.Sx \&SS . +.Ss \&sp +Insert vertical spaces into output with the following syntax: +.Bd -filled -offset indent +.Pf \. Sx \&sp +.Op Cm height +.Ed +.Pp +Insert +.Cm height +spaces, which must conform to +.Sx Scaling Widths . +If 0, this is equivalent to the +.Sx \&br +macro. +Defaults to 1, if unspecified. +.Pp +See also +.Sx \&br . +.Sh MACRO SYNTAX +The +.Nm +macros are classified by scope: line scope or block scope. +Line macros are only scoped to the current line (and, in some +situations, the subsequent line). +Block macros are scoped to the current line and subsequent lines until +closed by another block macro. +.Ss Line Macros +Line macros are generally scoped to the current line, with the body +consisting of zero or more arguments. +If a macro is scoped to the next line and the line arguments are empty, +the next line, which must be text, is used instead. +Thus: +.Bd -literal -offset indent +\&.I +foo +.Ed +.Pp +is equivalent to +.Sq \&.I foo . +If next-line macros are invoked consecutively, only the last is used. +If a next-line macro is followed by a non-next-line macro, an error is +raised, except for +.Sx \&br , +.Sx \&sp , +and +.Sx \&na . +.Pp +The syntax is as follows: +.Bd -literal -offset indent +\&.YO \(lBbody...\(rB +\(lBbody...\(rB +.Ed +.Bl -column "MacroX" "ArgumentsX" "ScopeXXXXX" "CompatX" -offset indent +.It Em Macro Ta Em Arguments Ta Em Scope Ta Em Notes +.It Sx \&AT Ta <=1 Ta current Ta \& +.It Sx \&B Ta n Ta next-line Ta \& +.It Sx \&BI Ta n Ta current Ta \& +.It Sx \&BR Ta n Ta current Ta \& +.It Sx \&DT Ta 0 Ta current Ta \& +.It Sx \&I Ta n Ta next-line Ta \& +.It Sx \&IB Ta n Ta current Ta \& +.It Sx \&IR Ta n Ta current Ta \& +.It Sx \&OP Ta 0, 1 Ta current Ta compat +.It Sx \&R Ta n Ta next-line Ta \& +.It Sx \&RB Ta n Ta current Ta \& +.It Sx \&RI Ta n Ta current Ta \& +.It Sx \&SB Ta n Ta next-line Ta \& +.It Sx \&SM Ta n Ta next-line Ta \& +.It Sx \&TH Ta >1, <6 Ta current Ta \& +.It Sx \&UC Ta <=1 Ta current Ta \& +.It Sx \&br Ta 0 Ta current Ta compat +.It Sx \&fi Ta 0 Ta current Ta compat +.It Sx \&ft Ta 1 Ta current Ta compat +.It Sx \&in Ta 1 Ta current Ta compat +.It Sx \&na Ta 0 Ta current Ta compat +.It Sx \&nf Ta 0 Ta current Ta compat +.It Sx \&sp Ta 1 Ta current Ta compat +.El +.Pp +Macros marked as +.Qq compat +are included for compatibility with the significant corpus of existing +manuals that mix dialects of roff. +These macros should not be used for portable +.Nm +manuals. +.Ss Block Macros +Block macros comprise a head and body. +As with in-line macros, the head is scoped to the current line and, in +one circumstance, the next line (the next-line stipulations as in +.Sx Line Macros +apply here as well). +.Pp +The syntax is as follows: +.Bd -literal -offset indent +\&.YO \(lBhead...\(rB +\(lBhead...\(rB +\(lBbody...\(rB +.Ed +.Pp +The closure of body scope may be to the section, where a macro is closed +by +.Sx \&SH ; +sub-section, closed by a section or +.Sx \&SS ; +part, closed by a section, sub-section, or +.Sx \&RE ; +or paragraph, closed by a section, sub-section, part, +.Sx \&HP , +.Sx \&IP , +.Sx \&LP , +.Sx \&P , +.Sx \&PP , +or +.Sx \&TP . +No closure refers to an explicit block closing macro. +.Pp +As a rule, block macros may not be nested; thus, calling a block macro +while another block macro scope is open, and the open scope is not +implicitly closed, is syntactically incorrect. +.Bl -column "MacroX" "ArgumentsX" "Head ScopeX" "sub-sectionX" "compatX" -offset indent +.It Em Macro Ta Em Arguments Ta Em Head Scope Ta Em Body Scope Ta Em Notes +.It Sx \&HP Ta <2 Ta current Ta paragraph Ta \& +.It Sx \&IP Ta <3 Ta current Ta paragraph Ta \& +.It Sx \&LP Ta 0 Ta current Ta paragraph Ta \& +.It Sx \&P Ta 0 Ta current Ta paragraph Ta \& +.It Sx \&PP Ta 0 Ta current Ta paragraph Ta \& +.It Sx \&RE Ta 0 Ta current Ta none Ta compat +.It Sx \&RS Ta 1 Ta current Ta part Ta compat +.It Sx \&SH Ta >0 Ta next-line Ta section Ta \& +.It Sx \&SS Ta >0 Ta next-line Ta sub-section Ta \& +.It Sx \&TP Ta n Ta next-line Ta paragraph Ta \& +.El +.Pp +Macros marked +.Qq compat +are as mentioned in +.Sx Line Macros . +.Pp +If a block macro is next-line scoped, it may only be followed by in-line +macros for decorating text. +.Ss Font handling +In +.Nm +documents, both +.Sx Physical markup +macros and +.Xr roff 7 +.Ql \ef +font escape sequences can be used to choose fonts. +In text lines, the effect of manual font selection by escape sequences +only lasts until the next macro invocation; in macro lines, it only lasts +until the end of the macro scope. +Note that macros like +.Sx \&BR +open and close a font scope for each argument. +.Sh COMPATIBILITY +This section documents areas of questionable portability between +implementations of the +.Nm +language. +.Pp +.Bl -dash -compact +.It +Do not depend on +.Sx \&SH +or +.Sx \&SS +to close out a literal context opened with +.Sx \&nf . +This behaviour may not be portable. +.It +In quoted literals, GNU troff allowed pair-wise double-quotes to produce +a standalone double-quote in formatted output. +It is not known whether this behaviour is exhibited by other formatters. +.It +troff suppresses a newline before +.Sq \(aq +macro output; in mandoc, it is an alias for the standard +.Sq \&. +control character. +.It +The +.Sq \eh +.Pq horizontal position , +.Sq \ev +.Pq vertical position , +.Sq \em +.Pq text colour , +.Sq \eM +.Pq text filling colour , +.Sq \ez +.Pq zero-length character , +.Sq \ew +.Pq string length , +.Sq \ek +.Pq horizontal position marker , +.Sq \eo +.Pq text overstrike , +and +.Sq \es +.Pq text size +escape sequences are all discarded in mandoc. +.It +The +.Sq \ef +scaling unit is accepted by mandoc, but rendered as the default unit. +.It +The +.Sx \&sp +macro does not accept negative values in mandoc. +In GNU troff, this would result in strange behaviour. +.It +In page header lines, GNU troff versions up to and including 1.21 +only print +.Ar volume +names explicitly specified in the +.Sx \&TH +macro; mandoc and newer groff print the default volume name +corresponding to the +.Ar section +number when no +.Ar volume +is given, like in +.Xr mdoc 7 . +.El +.Pp +The +.Sx OP +macro is part of the extended +.Nm +macro set, and may not be portable to non-GNU troff implementations. +.Sh SEE ALSO +.Xr man 1 , +.Xr mandoc 1 , +.Xr eqn 7 , +.Xr mandoc_char 7 , +.Xr mdoc 7 , +.Xr roff 7 , +.Xr tbl 7 +.Sh HISTORY +The +.Nm +language first appeared as a macro package for the roff typesetting +system in +.At v7 . +It was later rewritten by James Clark as a macro package for groff. +Eric S. Raymond wrote the extended +.Nm +macros for groff in 2007. +The stand-alone implementation that is part of the +.Xr mandoc 1 +utility written by Kristaps Dzonsons appeared in +.Ox 4.6 . +.Sh AUTHORS +This +.Nm +reference was written by +.An Kristaps Dzonsons , +.Mt kristaps@bsd.lv . +.Sh CAVEATS +Do not use this language. +Use +.Xr mdoc 7 , +instead. diff --git a/man.c b/man.c new file mode 100644 index 000000000000..1bea5610e3d7 --- /dev/null +++ b/man.c @@ -0,0 +1,690 @@ +/* $Id: man.c,v 1.115 2012/01/03 15:16:24 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/types.h> + +#include <assert.h> +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include "man.h" +#include "mandoc.h" +#include "libman.h" +#include "libmandoc.h" + +const char *const __man_macronames[MAN_MAX] = { + "br", "TH", "SH", "SS", + "TP", "LP", "PP", "P", + "IP", "HP", "SM", "SB", + "BI", "IB", "BR", "RB", + "R", "B", "I", "IR", + "RI", "na", "sp", "nf", + "fi", "RE", "RS", "DT", + "UC", "PD", "AT", "in", + "ft", "OP" + }; + +const char * const *man_macronames = __man_macronames; + +static struct man_node *man_node_alloc(struct man *, int, int, + enum man_type, enum mant); +static int man_node_append(struct man *, + struct man_node *); +static void man_node_free(struct man_node *); +static void man_node_unlink(struct man *, + struct man_node *); +static int man_ptext(struct man *, int, char *, int); +static int man_pmacro(struct man *, int, char *, int); +static void man_free1(struct man *); +static void man_alloc1(struct man *); +static int man_descope(struct man *, int, int); + + +const struct man_node * +man_node(const struct man *m) +{ + + assert( ! (MAN_HALT & m->flags)); + return(m->first); +} + + +const struct man_meta * +man_meta(const struct man *m) +{ + + assert( ! (MAN_HALT & m->flags)); + return(&m->meta); +} + + +void +man_reset(struct man *man) +{ + + man_free1(man); + man_alloc1(man); +} + + +void +man_free(struct man *man) +{ + + man_free1(man); + free(man); +} + + +struct man * +man_alloc(struct roff *roff, struct mparse *parse) +{ + struct man *p; + + p = mandoc_calloc(1, sizeof(struct man)); + + man_hash_init(); + p->parse = parse; + p->roff = roff; + + man_alloc1(p); + return(p); +} + + +int +man_endparse(struct man *m) +{ + + assert( ! (MAN_HALT & m->flags)); + if (man_macroend(m)) + return(1); + m->flags |= MAN_HALT; + return(0); +} + + +int +man_parseln(struct man *m, int ln, char *buf, int offs) +{ + + m->flags |= MAN_NEWLINE; + + assert( ! (MAN_HALT & m->flags)); + + return (mandoc_getcontrol(buf, &offs) ? + man_pmacro(m, ln, buf, offs) : + man_ptext(m, ln, buf, offs)); +} + + +static void +man_free1(struct man *man) +{ + + if (man->first) + man_node_delete(man, man->first); + if (man->meta.title) + free(man->meta.title); + if (man->meta.source) + free(man->meta.source); + if (man->meta.date) + free(man->meta.date); + if (man->meta.vol) + free(man->meta.vol); + if (man->meta.msec) + free(man->meta.msec); +} + + +static void +man_alloc1(struct man *m) +{ + + memset(&m->meta, 0, sizeof(struct man_meta)); + m->flags = 0; + m->last = mandoc_calloc(1, sizeof(struct man_node)); + m->first = m->last; + m->last->type = MAN_ROOT; + m->last->tok = MAN_MAX; + m->next = MAN_NEXT_CHILD; +} + + +static int +man_node_append(struct man *man, struct man_node *p) +{ + + assert(man->last); + assert(man->first); + assert(MAN_ROOT != p->type); + + switch (man->next) { + case (MAN_NEXT_SIBLING): + man->last->next = p; + p->prev = man->last; + p->parent = man->last->parent; + break; + case (MAN_NEXT_CHILD): + man->last->child = p; + p->parent = man->last; + break; + default: + abort(); + /* NOTREACHED */ + } + + assert(p->parent); + p->parent->nchild++; + + if ( ! man_valid_pre(man, p)) + return(0); + + switch (p->type) { + case (MAN_HEAD): + assert(MAN_BLOCK == p->parent->type); + p->parent->head = p; + break; + case (MAN_TAIL): + assert(MAN_BLOCK == p->parent->type); + p->parent->tail = p; + break; + case (MAN_BODY): + assert(MAN_BLOCK == p->parent->type); + p->parent->body = p; + break; + default: + break; + } + + man->last = p; + + switch (p->type) { + case (MAN_TBL): + /* FALLTHROUGH */ + case (MAN_TEXT): + if ( ! man_valid_post(man)) + return(0); + break; + default: + break; + } + + return(1); +} + + +static struct man_node * +man_node_alloc(struct man *m, int line, int pos, + enum man_type type, enum mant tok) +{ + struct man_node *p; + + p = mandoc_calloc(1, sizeof(struct man_node)); + p->line = line; + p->pos = pos; + p->type = type; + p->tok = tok; + + if (MAN_NEWLINE & m->flags) + p->flags |= MAN_LINE; + m->flags &= ~MAN_NEWLINE; + return(p); +} + + +int +man_elem_alloc(struct man *m, int line, int pos, enum mant tok) +{ + struct man_node *p; + + p = man_node_alloc(m, line, pos, MAN_ELEM, tok); + if ( ! man_node_append(m, p)) + return(0); + m->next = MAN_NEXT_CHILD; + return(1); +} + + +int +man_tail_alloc(struct man *m, int line, int pos, enum mant tok) +{ + struct man_node *p; + + p = man_node_alloc(m, line, pos, MAN_TAIL, tok); + if ( ! man_node_append(m, p)) + return(0); + m->next = MAN_NEXT_CHILD; + return(1); +} + + +int +man_head_alloc(struct man *m, int line, int pos, enum mant tok) +{ + struct man_node *p; + + p = man_node_alloc(m, line, pos, MAN_HEAD, tok); + if ( ! man_node_append(m, p)) + return(0); + m->next = MAN_NEXT_CHILD; + return(1); +} + + +int +man_body_alloc(struct man *m, int line, int pos, enum mant tok) +{ + struct man_node *p; + + p = man_node_alloc(m, line, pos, MAN_BODY, tok); + if ( ! man_node_append(m, p)) + return(0); + m->next = MAN_NEXT_CHILD; + return(1); +} + + +int +man_block_alloc(struct man *m, int line, int pos, enum mant tok) +{ + struct man_node *p; + + p = man_node_alloc(m, line, pos, MAN_BLOCK, tok); + if ( ! man_node_append(m, p)) + return(0); + m->next = MAN_NEXT_CHILD; + return(1); +} + +int +man_word_alloc(struct man *m, int line, int pos, const char *word) +{ + struct man_node *n; + + n = man_node_alloc(m, line, pos, MAN_TEXT, MAN_MAX); + n->string = roff_strdup(m->roff, word); + + if ( ! man_node_append(m, n)) + return(0); + + m->next = MAN_NEXT_SIBLING; + return(1); +} + + +/* + * Free all of the resources held by a node. This does NOT unlink a + * node from its context; for that, see man_node_unlink(). + */ +static void +man_node_free(struct man_node *p) +{ + + if (p->string) + free(p->string); + free(p); +} + + +void +man_node_delete(struct man *m, struct man_node *p) +{ + + while (p->child) + man_node_delete(m, p->child); + + man_node_unlink(m, p); + man_node_free(p); +} + +int +man_addeqn(struct man *m, const struct eqn *ep) +{ + struct man_node *n; + + assert( ! (MAN_HALT & m->flags)); + + n = man_node_alloc(m, ep->ln, ep->pos, MAN_EQN, MAN_MAX); + n->eqn = ep; + + if ( ! man_node_append(m, n)) + return(0); + + m->next = MAN_NEXT_SIBLING; + return(man_descope(m, ep->ln, ep->pos)); +} + +int +man_addspan(struct man *m, const struct tbl_span *sp) +{ + struct man_node *n; + + assert( ! (MAN_HALT & m->flags)); + + n = man_node_alloc(m, sp->line, 0, MAN_TBL, MAN_MAX); + n->span = sp; + + if ( ! man_node_append(m, n)) + return(0); + + m->next = MAN_NEXT_SIBLING; + return(man_descope(m, sp->line, 0)); +} + +static int +man_descope(struct man *m, int line, int offs) +{ + /* + * Co-ordinate what happens with having a next-line scope open: + * first close out the element scope (if applicable), then close + * out the block scope (also if applicable). + */ + + if (MAN_ELINE & m->flags) { + m->flags &= ~MAN_ELINE; + if ( ! man_unscope(m, m->last->parent, MANDOCERR_MAX)) + return(0); + } + + if ( ! (MAN_BLINE & m->flags)) + return(1); + m->flags &= ~MAN_BLINE; + + if ( ! man_unscope(m, m->last->parent, MANDOCERR_MAX)) + return(0); + return(man_body_alloc(m, line, offs, m->last->tok)); +} + +static int +man_ptext(struct man *m, int line, char *buf, int offs) +{ + int i; + + /* Literal free-form text whitespace is preserved. */ + + if (MAN_LITERAL & m->flags) { + if ( ! man_word_alloc(m, line, offs, buf + offs)) + return(0); + return(man_descope(m, line, offs)); + } + + /* Pump blank lines directly into the backend. */ + + for (i = offs; ' ' == buf[i]; i++) + /* Skip leading whitespace. */ ; + + if ('\0' == buf[i]) { + /* Allocate a blank entry. */ + if ( ! man_word_alloc(m, line, offs, "")) + return(0); + return(man_descope(m, line, offs)); + } + + /* + * Warn if the last un-escaped character is whitespace. Then + * strip away the remaining spaces (tabs stay!). + */ + + i = (int)strlen(buf); + assert(i); + + if (' ' == buf[i - 1] || '\t' == buf[i - 1]) { + if (i > 1 && '\\' != buf[i - 2]) + man_pmsg(m, line, i - 1, MANDOCERR_EOLNSPACE); + + for (--i; i && ' ' == buf[i]; i--) + /* Spin back to non-space. */ ; + + /* Jump ahead of escaped whitespace. */ + i += '\\' == buf[i] ? 2 : 1; + + buf[i] = '\0'; + } + + if ( ! man_word_alloc(m, line, offs, buf + offs)) + return(0); + + /* + * End-of-sentence check. If the last character is an unescaped + * EOS character, then flag the node as being the end of a + * sentence. The front-end will know how to interpret this. + */ + + assert(i); + if (mandoc_eos(buf, (size_t)i, 0)) + m->last->flags |= MAN_EOS; + + return(man_descope(m, line, offs)); +} + +static int +man_pmacro(struct man *m, int ln, char *buf, int offs) +{ + int i, ppos; + enum mant tok; + char mac[5]; + struct man_node *n; + + if ('"' == buf[offs]) { + man_pmsg(m, ln, offs, MANDOCERR_BADCOMMENT); + return(1); + } else if ('\0' == buf[offs]) + return(1); + + ppos = offs; + + /* + * Copy the first word into a nil-terminated buffer. + * Stop copying when a tab, space, or eoln is encountered. + */ + + i = 0; + while (i < 4 && '\0' != buf[offs] && + ' ' != buf[offs] && '\t' != buf[offs]) + mac[i++] = buf[offs++]; + + mac[i] = '\0'; + + tok = (i > 0 && i < 4) ? man_hash_find(mac) : MAN_MAX; + + if (MAN_MAX == tok) { + mandoc_vmsg(MANDOCERR_MACRO, m->parse, ln, + ppos, "%s", buf + ppos - 1); + return(1); + } + + /* The macro is sane. Jump to the next word. */ + + while (buf[offs] && ' ' == buf[offs]) + offs++; + + /* + * Trailing whitespace. Note that tabs are allowed to be passed + * into the parser as "text", so we only warn about spaces here. + */ + + if ('\0' == buf[offs] && ' ' == buf[offs - 1]) + man_pmsg(m, ln, offs - 1, MANDOCERR_EOLNSPACE); + + /* + * Remove prior ELINE macro, as it's being clobbered by a new + * macro. Note that NSCOPED macros do not close out ELINE + * macros---they don't print text---so we let those slip by. + */ + + if ( ! (MAN_NSCOPED & man_macros[tok].flags) && + m->flags & MAN_ELINE) { + n = m->last; + assert(MAN_TEXT != n->type); + + /* Remove repeated NSCOPED macros causing ELINE. */ + + if (MAN_NSCOPED & man_macros[n->tok].flags) + n = n->parent; + + mandoc_vmsg(MANDOCERR_LINESCOPE, m->parse, n->line, + n->pos, "%s breaks %s", man_macronames[tok], + man_macronames[n->tok]); + + man_node_delete(m, n); + m->flags &= ~MAN_ELINE; + } + + /* + * Remove prior BLINE macro that is being clobbered. + */ + if ((m->flags & MAN_BLINE) && + (MAN_BSCOPE & man_macros[tok].flags)) { + n = m->last; + + /* Might be a text node like 8 in + * .TP 8 + * .SH foo + */ + if (MAN_TEXT == n->type) + n = n->parent; + + /* Remove element that didn't end BLINE, if any. */ + if ( ! (MAN_BSCOPE & man_macros[n->tok].flags)) + n = n->parent; + + assert(MAN_HEAD == n->type); + n = n->parent; + assert(MAN_BLOCK == n->type); + assert(MAN_SCOPED & man_macros[n->tok].flags); + + mandoc_vmsg(MANDOCERR_LINESCOPE, m->parse, n->line, + n->pos, "%s breaks %s", man_macronames[tok], + man_macronames[n->tok]); + + man_node_delete(m, n); + m->flags &= ~MAN_BLINE; + } + + /* + * Save the fact that we're in the next-line for a block. In + * this way, embedded roff instructions can "remember" state + * when they exit. + */ + + if (MAN_BLINE & m->flags) + m->flags |= MAN_BPLINE; + + /* Call to handler... */ + + assert(man_macros[tok].fp); + if ( ! (*man_macros[tok].fp)(m, tok, ln, ppos, &offs, buf)) + goto err; + + /* + * We weren't in a block-line scope when entering the + * above-parsed macro, so return. + */ + + if ( ! (MAN_BPLINE & m->flags)) { + m->flags &= ~MAN_ILINE; + return(1); + } + m->flags &= ~MAN_BPLINE; + + /* + * If we're in a block scope, then allow this macro to slip by + * without closing scope around it. + */ + + if (MAN_ILINE & m->flags) { + m->flags &= ~MAN_ILINE; + return(1); + } + + /* + * If we've opened a new next-line element scope, then return + * now, as the next line will close out the block scope. + */ + + if (MAN_ELINE & m->flags) + return(1); + + /* Close out the block scope opened in the prior line. */ + + assert(MAN_BLINE & m->flags); + m->flags &= ~MAN_BLINE; + + if ( ! man_unscope(m, m->last->parent, MANDOCERR_MAX)) + return(0); + return(man_body_alloc(m, ln, ppos, m->last->tok)); + +err: /* Error out. */ + + m->flags |= MAN_HALT; + return(0); +} + +/* + * Unlink a node from its context. If "m" is provided, the last parse + * point will also be adjusted accordingly. + */ +static void +man_node_unlink(struct man *m, struct man_node *n) +{ + + /* Adjust siblings. */ + + if (n->prev) + n->prev->next = n->next; + if (n->next) + n->next->prev = n->prev; + + /* Adjust parent. */ + + if (n->parent) { + n->parent->nchild--; + if (n->parent->child == n) + n->parent->child = n->prev ? n->prev : n->next; + } + + /* Adjust parse point, if applicable. */ + + if (m && m->last == n) { + /*XXX: this can occur when bailing from validation. */ + /*assert(NULL == n->next);*/ + if (n->prev) { + m->last = n->prev; + m->next = MAN_NEXT_SIBLING; + } else { + m->last = n->parent; + m->next = MAN_NEXT_CHILD; + } + } + + if (m && m->first == n) + m->first = NULL; +} + +const struct mparse * +man_mparse(const struct man *m) +{ + + assert(m && m->parse); + return(m->parse); +} diff --git a/man.cgi.7 b/man.cgi.7 new file mode 100644 index 000000000000..b7afd84b907a --- /dev/null +++ b/man.cgi.7 @@ -0,0 +1,123 @@ +.Dd $Mdocdate: March 24 2012 $ +.Dt MAN.CGI 7 +.Os +.Sh NAME +.Nm man.cgi +.Nd cgi for manpage query and display +.Sh SYNOPSIS +.Nm +.Sh DESCRIPTION +The +.Nm +script queries and displays manual pages. +It interfaces with +.Xr mandocdb 8 +databases cached with +.Xr catman 8 . +.Pp +To use +.Nm , +create a manual cache in +.Xr catman 8 . +Assign this directory to the environment variable +.Ev CACHE_DIR , +defaulting to +.Pa /cache/man.cgi . +Copy the +.Pa man.cgi +script into your CGI directory (see +.Sx FILES +for other relevant files). +.Pp +Multiple +.Xr catman 8 +trees may be managed by +.Nm : +directories under +.Ev CACHE_DIR +containing +.Pa etc/catman.conf +are identified as +.Qq manroots . +The path of a manroot under +.Ev CACHE_DIR +is converted to a name by replacing path separators with spaces. +.Pp +Thus, if +.Ev CACHE_DIR +is the default +.Pa /cache/man.cgi , +the web-server is jailed to +.Pa /var/www , +and cache subdirectories +.Pa ./foo/1 +and +.Pa ./bar/2 +contain +.Pa etc/catman.conf , +.Nm +will assign these to manroots +.Qq foo 1 +and +.Qq bar 2 , +respectively. +These names will appear as choices when searching for manuals. +.Pp +If +.Nm +finds only one manroot, or none, then the selection box is omitted. +If no manroot is specified during search, the first manroot is used by +default. +.Sh ENVIRONMENT +.Bl -tag -width Ds +.It Ev CACHE_DIR +The absolute path of the +.Xr catman 8 +cache directory. +This must not have a trailing slash. +.It Ev CSS_DIR +Prepended to CSS file links in outputted HTML files. +This must not have a trailing slash. +.El +.Sh FILES +.Bl -tag -width Ds +.It Pa etc/catman.conf +Built by +.Xr catman 8 +and must exist at least once under the configuration directory root. +.It Pa man.css +Should be visible in the server document root or within +.Ev CSS_DIR . +Included in each page after +.Pa man-cgi.css , +ostensibly for +.Xr mandoc 1 +HTML output styling. +.It Pa man.cgi.css +Should be visible in the server document root or within +.Ev CSS_DIR . +Included in each page, ostensibly for general +.Nm +styling. +.El +.Sh COMPATIBILITY +The +.Nm +script is call-compatible with queries from the traditional +.Pa man.cgi +script by Wolfram Schneider. +However, the results may not be quite the same. +.Sh SEE ALSO +.Xr catman 8 , +.Xr mandocdb 8 +.Sh AUTHORS +The +.Nm +utility was written by +.An Kristaps Dzonsons , +.Mt kristaps@bsd.lv . +.Sh CAVEATS +If you're running in a jailed web-server, make sure the +.Pa /tmp +directory exists and is writable. +The databases may need this for scratch space. diff --git a/man.h b/man.h new file mode 100644 index 000000000000..4fc3934e6f6f --- /dev/null +++ b/man.h @@ -0,0 +1,113 @@ +/* $Id: man.h,v 1.60 2012/01/03 15:16:24 kristaps Exp $ */ +/* + * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef MAN_H +#define MAN_H + +enum mant { + MAN_br = 0, + MAN_TH, + MAN_SH, + MAN_SS, + MAN_TP, + MAN_LP, + MAN_PP, + MAN_P, + MAN_IP, + MAN_HP, + MAN_SM, + MAN_SB, + MAN_BI, + MAN_IB, + MAN_BR, + MAN_RB, + MAN_R, + MAN_B, + MAN_I, + MAN_IR, + MAN_RI, + MAN_na, + MAN_sp, + MAN_nf, + MAN_fi, + MAN_RE, + MAN_RS, + MAN_DT, + MAN_UC, + MAN_PD, + MAN_AT, + MAN_in, + MAN_ft, + MAN_OP, + MAN_MAX +}; + +enum man_type { + MAN_TEXT, + MAN_ELEM, + MAN_ROOT, + MAN_BLOCK, + MAN_HEAD, + MAN_BODY, + MAN_TAIL, + MAN_TBL, + MAN_EQN +}; + +struct man_meta { + char *msec; /* `TH' section (1, 3p, etc.) */ + char *date; /* `TH' normalised date */ + char *vol; /* `TH' volume */ + char *title; /* `TH' title (e.g., FOO) */ + char *source; /* `TH' source (e.g., GNU) */ +}; + +struct man_node { + struct man_node *parent; /* parent AST node */ + struct man_node *child; /* first child AST node */ + struct man_node *next; /* sibling AST node */ + struct man_node *prev; /* prior sibling AST node */ + int nchild; /* number children */ + int line; + int pos; + enum mant tok; /* tok or MAN__MAX if none */ + int flags; +#define MAN_VALID (1 << 0) /* has been validated */ +#define MAN_EOS (1 << 2) /* at sentence boundary */ +#define MAN_LINE (1 << 3) /* first macro/text on line */ + enum man_type type; /* AST node type */ + char *string; /* TEXT node argument */ + struct man_node *head; /* BLOCK node HEAD ptr */ + struct man_node *tail; /* BLOCK node TAIL ptr */ + struct man_node *body; /* BLOCK node BODY ptr */ + const struct tbl_span *span; /* TBL */ + const struct eqn *eqn; /* EQN */ +}; + +/* Names of macros. Index is enum mant. */ +extern const char *const *man_macronames; + +__BEGIN_DECLS + +struct man; + +const struct man_node *man_node(const struct man *); +const struct man_meta *man_meta(const struct man *); +const struct mparse *man_mparse(const struct man *); + +__END_DECLS + +#endif /*!MAN_H*/ diff --git a/man_hash.c b/man_hash.c new file mode 100644 index 000000000000..86c5c40a199b --- /dev/null +++ b/man_hash.c @@ -0,0 +1,107 @@ +/* $Id: man_hash.c,v 1.25 2011/07/24 18:15:14 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/types.h> + +#include <assert.h> +#include <ctype.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> + +#include "man.h" +#include "mandoc.h" +#include "libman.h" + +#define HASH_DEPTH 6 + +#define HASH_ROW(x) do { \ + if (isupper((unsigned char)(x))) \ + (x) -= 65; \ + else \ + (x) -= 97; \ + (x) *= HASH_DEPTH; \ + } while (/* CONSTCOND */ 0) + +/* + * Lookup table is indexed first by lower-case first letter (plus one + * for the period, which is stored in the last row), then by lower or + * uppercase second letter. Buckets correspond to the index of the + * macro (the integer value of the enum stored as a char to save a bit + * of space). + */ +static unsigned char table[26 * HASH_DEPTH]; + +/* + * XXX - this hash has global scope, so if intended for use as a library + * with multiple callers, it will need re-invocation protection. + */ +void +man_hash_init(void) +{ + int i, j, x; + + memset(table, UCHAR_MAX, sizeof(table)); + + assert(/* LINTED */ + MAN_MAX < UCHAR_MAX); + + for (i = 0; i < (int)MAN_MAX; i++) { + x = man_macronames[i][0]; + + assert(isalpha((unsigned char)x)); + + HASH_ROW(x); + + for (j = 0; j < HASH_DEPTH; j++) + if (UCHAR_MAX == table[x + j]) { + table[x + j] = (unsigned char)i; + break; + } + + assert(j < HASH_DEPTH); + } +} + + +enum mant +man_hash_find(const char *tmp) +{ + int x, y, i; + enum mant tok; + + if ('\0' == (x = tmp[0])) + return(MAN_MAX); + if ( ! (isalpha((unsigned char)x))) + return(MAN_MAX); + + HASH_ROW(x); + + for (i = 0; i < HASH_DEPTH; i++) { + if (UCHAR_MAX == (y = table[x + i])) + return(MAN_MAX); + + tok = (enum mant)y; + if (0 == strcmp(tmp, man_macronames[tok])) + return(tok); + } + + return(MAN_MAX); +} diff --git a/man_html.c b/man_html.c new file mode 100644 index 000000000000..a76ea2d70708 --- /dev/null +++ b/man_html.c @@ -0,0 +1,688 @@ +/* $Id: man_html.c,v 1.86 2012/01/03 15:16:24 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/types.h> + +#include <assert.h> +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "mandoc.h" +#include "out.h" +#include "html.h" +#include "man.h" +#include "main.h" + +/* TODO: preserve ident widths. */ +/* FIXME: have PD set the default vspace width. */ + +#define INDENT 5 + +#define MAN_ARGS const struct man_meta *m, \ + const struct man_node *n, \ + struct mhtml *mh, \ + struct html *h + +struct mhtml { + int fl; +#define MANH_LITERAL (1 << 0) /* literal context */ +}; + +struct htmlman { + int (*pre)(MAN_ARGS); + int (*post)(MAN_ARGS); +}; + +static void print_bvspace(struct html *, + const struct man_node *); +static void print_man(MAN_ARGS); +static void print_man_head(MAN_ARGS); +static void print_man_nodelist(MAN_ARGS); +static void print_man_node(MAN_ARGS); +static int a2width(const struct man_node *, + struct roffsu *); +static int man_B_pre(MAN_ARGS); +static int man_HP_pre(MAN_ARGS); +static int man_IP_pre(MAN_ARGS); +static int man_I_pre(MAN_ARGS); +static int man_OP_pre(MAN_ARGS); +static int man_PP_pre(MAN_ARGS); +static int man_RS_pre(MAN_ARGS); +static int man_SH_pre(MAN_ARGS); +static int man_SM_pre(MAN_ARGS); +static int man_SS_pre(MAN_ARGS); +static int man_alt_pre(MAN_ARGS); +static int man_br_pre(MAN_ARGS); +static int man_ign_pre(MAN_ARGS); +static int man_in_pre(MAN_ARGS); +static int man_literal_pre(MAN_ARGS); +static void man_root_post(MAN_ARGS); +static void man_root_pre(MAN_ARGS); + +static const struct htmlman mans[MAN_MAX] = { + { man_br_pre, NULL }, /* br */ + { NULL, NULL }, /* TH */ + { man_SH_pre, NULL }, /* SH */ + { man_SS_pre, NULL }, /* SS */ + { man_IP_pre, NULL }, /* TP */ + { man_PP_pre, NULL }, /* LP */ + { man_PP_pre, NULL }, /* PP */ + { man_PP_pre, NULL }, /* P */ + { man_IP_pre, NULL }, /* IP */ + { man_HP_pre, NULL }, /* HP */ + { man_SM_pre, NULL }, /* SM */ + { man_SM_pre, NULL }, /* SB */ + { man_alt_pre, NULL }, /* BI */ + { man_alt_pre, NULL }, /* IB */ + { man_alt_pre, NULL }, /* BR */ + { man_alt_pre, NULL }, /* RB */ + { NULL, NULL }, /* R */ + { man_B_pre, NULL }, /* B */ + { man_I_pre, NULL }, /* I */ + { man_alt_pre, NULL }, /* IR */ + { man_alt_pre, NULL }, /* RI */ + { man_ign_pre, NULL }, /* na */ + { man_br_pre, NULL }, /* sp */ + { man_literal_pre, NULL }, /* nf */ + { man_literal_pre, NULL }, /* fi */ + { NULL, NULL }, /* RE */ + { man_RS_pre, NULL }, /* RS */ + { man_ign_pre, NULL }, /* DT */ + { man_ign_pre, NULL }, /* UC */ + { man_ign_pre, NULL }, /* PD */ + { man_ign_pre, NULL }, /* AT */ + { man_in_pre, NULL }, /* in */ + { man_ign_pre, NULL }, /* ft */ + { man_OP_pre, NULL }, /* OP */ +}; + +/* + * Printing leading vertical space before a block. + * This is used for the paragraph macros. + * The rules are pretty simple, since there's very little nesting going + * on here. Basically, if we're the first within another block (SS/SH), + * then don't emit vertical space. If we are (RS), then do. If not the + * first, print it. + */ +static void +print_bvspace(struct html *h, const struct man_node *n) +{ + + if (n->body && n->body->child) + if (MAN_TBL == n->body->child->type) + return; + + if (MAN_ROOT == n->parent->type || MAN_RS != n->parent->tok) + if (NULL == n->prev) + return; + + print_otag(h, TAG_P, 0, NULL); +} + +void +html_man(void *arg, const struct man *m) +{ + struct mhtml mh; + + memset(&mh, 0, sizeof(struct mhtml)); + print_man(man_meta(m), man_node(m), &mh, (struct html *)arg); + putchar('\n'); +} + +static void +print_man(MAN_ARGS) +{ + struct tag *t, *tt; + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "mandoc"); + + if ( ! (HTML_FRAGMENT & h->oflags)) { + print_gen_decls(h); + t = print_otag(h, TAG_HTML, 0, NULL); + tt = print_otag(h, TAG_HEAD, 0, NULL); + print_man_head(m, n, mh, h); + print_tagq(h, tt); + print_otag(h, TAG_BODY, 0, NULL); + print_otag(h, TAG_DIV, 1, &tag); + } else + t = print_otag(h, TAG_DIV, 1, &tag); + + print_man_nodelist(m, n, mh, h); + print_tagq(h, t); +} + + +/* ARGSUSED */ +static void +print_man_head(MAN_ARGS) +{ + + print_gen_head(h); + assert(m->title); + assert(m->msec); + bufcat_fmt(h, "%s(%s)", m->title, m->msec); + print_otag(h, TAG_TITLE, 0, NULL); + print_text(h, h->buf); +} + + +static void +print_man_nodelist(MAN_ARGS) +{ + + print_man_node(m, n, mh, h); + if (n->next) + print_man_nodelist(m, n->next, mh, h); +} + + +static void +print_man_node(MAN_ARGS) +{ + int child; + struct tag *t; + + child = 1; + t = h->tags.head; + + switch (n->type) { + case (MAN_ROOT): + man_root_pre(m, n, mh, h); + break; + case (MAN_TEXT): + /* + * If we have a blank line, output a vertical space. + * If we have a space as the first character, break + * before printing the line's data. + */ + if ('\0' == *n->string) { + print_otag(h, TAG_P, 0, NULL); + return; + } + + if (' ' == *n->string && MAN_LINE & n->flags) + print_otag(h, TAG_BR, 0, NULL); + else if (MANH_LITERAL & mh->fl && n->prev) + print_otag(h, TAG_BR, 0, NULL); + + print_text(h, n->string); + return; + case (MAN_EQN): + print_eqn(h, n->eqn); + break; + case (MAN_TBL): + /* + * This will take care of initialising all of the table + * state data for the first table, then tearing it down + * for the last one. + */ + print_tbl(h, n->span); + return; + default: + /* + * Close out scope of font prior to opening a macro + * scope. + */ + if (HTMLFONT_NONE != h->metac) { + h->metal = h->metac; + h->metac = HTMLFONT_NONE; + } + + /* + * Close out the current table, if it's open, and unset + * the "meta" table state. This will be reopened on the + * next table element. + */ + if (h->tblt) { + print_tblclose(h); + t = h->tags.head; + } + if (mans[n->tok].pre) + child = (*mans[n->tok].pre)(m, n, mh, h); + break; + } + + if (child && n->child) + print_man_nodelist(m, n->child, mh, h); + + /* This will automatically close out any font scope. */ + print_stagq(h, t); + + switch (n->type) { + case (MAN_ROOT): + man_root_post(m, n, mh, h); + break; + case (MAN_EQN): + break; + default: + if (mans[n->tok].post) + (*mans[n->tok].post)(m, n, mh, h); + break; + } +} + + +static int +a2width(const struct man_node *n, struct roffsu *su) +{ + + if (MAN_TEXT != n->type) + return(0); + if (a2roffsu(n->string, su, SCALE_BU)) + return(1); + + return(0); +} + + +/* ARGSUSED */ +static void +man_root_pre(MAN_ARGS) +{ + struct htmlpair tag[3]; + struct tag *t, *tt; + char b[BUFSIZ], title[BUFSIZ]; + + b[0] = 0; + if (m->vol) + (void)strlcat(b, m->vol, BUFSIZ); + + assert(m->title); + assert(m->msec); + snprintf(title, BUFSIZ - 1, "%s(%s)", m->title, m->msec); + + PAIR_SUMMARY_INIT(&tag[0], "Document Header"); + PAIR_CLASS_INIT(&tag[1], "head"); + PAIR_INIT(&tag[2], ATTR_WIDTH, "100%"); + t = print_otag(h, TAG_TABLE, 3, tag); + PAIR_INIT(&tag[0], ATTR_WIDTH, "30%"); + print_otag(h, TAG_COL, 1, tag); + print_otag(h, TAG_COL, 1, tag); + print_otag(h, TAG_COL, 1, tag); + + print_otag(h, TAG_TBODY, 0, NULL); + + tt = print_otag(h, TAG_TR, 0, NULL); + + PAIR_CLASS_INIT(&tag[0], "head-ltitle"); + print_otag(h, TAG_TD, 1, tag); + print_text(h, title); + print_stagq(h, tt); + + PAIR_CLASS_INIT(&tag[0], "head-vol"); + PAIR_INIT(&tag[1], ATTR_ALIGN, "center"); + print_otag(h, TAG_TD, 2, tag); + print_text(h, b); + print_stagq(h, tt); + + PAIR_CLASS_INIT(&tag[0], "head-rtitle"); + PAIR_INIT(&tag[1], ATTR_ALIGN, "right"); + print_otag(h, TAG_TD, 2, tag); + print_text(h, title); + print_tagq(h, t); +} + + +/* ARGSUSED */ +static void +man_root_post(MAN_ARGS) +{ + struct htmlpair tag[3]; + struct tag *t, *tt; + + PAIR_SUMMARY_INIT(&tag[0], "Document Footer"); + PAIR_CLASS_INIT(&tag[1], "foot"); + PAIR_INIT(&tag[2], ATTR_WIDTH, "100%"); + t = print_otag(h, TAG_TABLE, 3, tag); + PAIR_INIT(&tag[0], ATTR_WIDTH, "50%"); + print_otag(h, TAG_COL, 1, tag); + print_otag(h, TAG_COL, 1, tag); + + tt = print_otag(h, TAG_TR, 0, NULL); + + PAIR_CLASS_INIT(&tag[0], "foot-date"); + print_otag(h, TAG_TD, 1, tag); + + assert(m->date); + print_text(h, m->date); + print_stagq(h, tt); + + PAIR_CLASS_INIT(&tag[0], "foot-os"); + PAIR_INIT(&tag[1], ATTR_ALIGN, "right"); + print_otag(h, TAG_TD, 2, tag); + + if (m->source) + print_text(h, m->source); + print_tagq(h, t); +} + + +/* ARGSUSED */ +static int +man_br_pre(MAN_ARGS) +{ + struct roffsu su; + struct htmlpair tag; + + SCALE_VS_INIT(&su, 1); + + if (MAN_sp == n->tok) { + if (NULL != (n = n->child)) + if ( ! a2roffsu(n->string, &su, SCALE_VS)) + SCALE_VS_INIT(&su, atoi(n->string)); + } else + su.scale = 0; + + bufinit(h); + bufcat_su(h, "height", &su); + PAIR_STYLE_INIT(&tag, h); + print_otag(h, TAG_DIV, 1, &tag); + + /* So the div isn't empty: */ + print_text(h, "\\~"); + + return(0); +} + +/* ARGSUSED */ +static int +man_SH_pre(MAN_ARGS) +{ + struct htmlpair tag; + + if (MAN_BLOCK == n->type) { + mh->fl &= ~MANH_LITERAL; + PAIR_CLASS_INIT(&tag, "section"); + print_otag(h, TAG_DIV, 1, &tag); + return(1); + } else if (MAN_BODY == n->type) + return(1); + + print_otag(h, TAG_H1, 0, NULL); + return(1); +} + +/* ARGSUSED */ +static int +man_alt_pre(MAN_ARGS) +{ + const struct man_node *nn; + int i, savelit; + enum htmltag fp; + struct tag *t; + + if ((savelit = mh->fl & MANH_LITERAL)) + print_otag(h, TAG_BR, 0, NULL); + + mh->fl &= ~MANH_LITERAL; + + for (i = 0, nn = n->child; nn; nn = nn->next, i++) { + t = NULL; + switch (n->tok) { + case (MAN_BI): + fp = i % 2 ? TAG_I : TAG_B; + break; + case (MAN_IB): + fp = i % 2 ? TAG_B : TAG_I; + break; + case (MAN_RI): + fp = i % 2 ? TAG_I : TAG_MAX; + break; + case (MAN_IR): + fp = i % 2 ? TAG_MAX : TAG_I; + break; + case (MAN_BR): + fp = i % 2 ? TAG_MAX : TAG_B; + break; + case (MAN_RB): + fp = i % 2 ? TAG_B : TAG_MAX; + break; + default: + abort(); + /* NOTREACHED */ + } + + if (i) + h->flags |= HTML_NOSPACE; + + if (TAG_MAX != fp) + t = print_otag(h, fp, 0, NULL); + + print_man_node(m, nn, mh, h); + + if (t) + print_tagq(h, t); + } + + if (savelit) + mh->fl |= MANH_LITERAL; + + return(0); +} + +/* ARGSUSED */ +static int +man_SM_pre(MAN_ARGS) +{ + + print_otag(h, TAG_SMALL, 0, NULL); + if (MAN_SB == n->tok) + print_otag(h, TAG_B, 0, NULL); + return(1); +} + +/* ARGSUSED */ +static int +man_SS_pre(MAN_ARGS) +{ + struct htmlpair tag; + + if (MAN_BLOCK == n->type) { + mh->fl &= ~MANH_LITERAL; + PAIR_CLASS_INIT(&tag, "subsection"); + print_otag(h, TAG_DIV, 1, &tag); + return(1); + } else if (MAN_BODY == n->type) + return(1); + + print_otag(h, TAG_H2, 0, NULL); + return(1); +} + +/* ARGSUSED */ +static int +man_PP_pre(MAN_ARGS) +{ + + if (MAN_HEAD == n->type) + return(0); + else if (MAN_BLOCK == n->type) + print_bvspace(h, n); + + return(1); +} + +/* ARGSUSED */ +static int +man_IP_pre(MAN_ARGS) +{ + const struct man_node *nn; + + if (MAN_BODY == n->type) { + print_otag(h, TAG_DD, 0, NULL); + return(1); + } else if (MAN_HEAD != n->type) { + print_otag(h, TAG_DL, 0, NULL); + return(1); + } + + /* FIXME: width specification. */ + + print_otag(h, TAG_DT, 0, NULL); + + /* For IP, only print the first header element. */ + + if (MAN_IP == n->tok && n->child) + print_man_node(m, n->child, mh, h); + + /* For TP, only print next-line header elements. */ + + if (MAN_TP == n->tok) + for (nn = n->child; nn; nn = nn->next) + if (nn->line > n->line) + print_man_node(m, nn, mh, h); + + return(0); +} + +/* ARGSUSED */ +static int +man_HP_pre(MAN_ARGS) +{ + struct htmlpair tag; + struct roffsu su; + const struct man_node *np; + + if (MAN_HEAD == n->type) + return(0); + else if (MAN_BLOCK != n->type) + return(1); + + np = n->head->child; + + if (NULL == np || ! a2width(np, &su)) + SCALE_HS_INIT(&su, INDENT); + + bufinit(h); + + print_bvspace(h, n); + bufcat_su(h, "margin-left", &su); + su.scale = -su.scale; + bufcat_su(h, "text-indent", &su); + PAIR_STYLE_INIT(&tag, h); + print_otag(h, TAG_P, 1, &tag); + return(1); +} + +/* ARGSUSED */ +static int +man_OP_pre(MAN_ARGS) +{ + struct tag *tt; + struct htmlpair tag; + + print_text(h, "["); + h->flags |= HTML_NOSPACE; + PAIR_CLASS_INIT(&tag, "opt"); + tt = print_otag(h, TAG_SPAN, 1, &tag); + + if (NULL != (n = n->child)) { + print_otag(h, TAG_B, 0, NULL); + print_text(h, n->string); + } + + print_stagq(h, tt); + + if (NULL != n && NULL != n->next) { + print_otag(h, TAG_I, 0, NULL); + print_text(h, n->next->string); + } + + print_stagq(h, tt); + h->flags |= HTML_NOSPACE; + print_text(h, "]"); + return(0); +} + + +/* ARGSUSED */ +static int +man_B_pre(MAN_ARGS) +{ + + print_otag(h, TAG_B, 0, NULL); + return(1); +} + +/* ARGSUSED */ +static int +man_I_pre(MAN_ARGS) +{ + + print_otag(h, TAG_I, 0, NULL); + return(1); +} + +/* ARGSUSED */ +static int +man_literal_pre(MAN_ARGS) +{ + + if (MAN_nf != n->tok) { + print_otag(h, TAG_BR, 0, NULL); + mh->fl &= ~MANH_LITERAL; + } else + mh->fl |= MANH_LITERAL; + + return(0); +} + +/* ARGSUSED */ +static int +man_in_pre(MAN_ARGS) +{ + + print_otag(h, TAG_BR, 0, NULL); + return(0); +} + +/* ARGSUSED */ +static int +man_ign_pre(MAN_ARGS) +{ + + return(0); +} + +/* ARGSUSED */ +static int +man_RS_pre(MAN_ARGS) +{ + struct htmlpair tag; + struct roffsu su; + + if (MAN_HEAD == n->type) + return(0); + else if (MAN_BODY == n->type) + return(1); + + SCALE_HS_INIT(&su, INDENT); + if (n->head->child) + a2width(n->head->child, &su); + + bufinit(h); + bufcat_su(h, "margin-left", &su); + PAIR_STYLE_INIT(&tag, h); + print_otag(h, TAG_DIV, 1, &tag); + return(1); +} diff --git a/man_macro.c b/man_macro.c new file mode 100644 index 000000000000..4bbbc4fa7f16 --- /dev/null +++ b/man_macro.c @@ -0,0 +1,484 @@ +/* $Id: man_macro.c,v 1.71 2012/01/03 15:16:24 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <assert.h> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> + +#include "man.h" +#include "mandoc.h" +#include "libmandoc.h" +#include "libman.h" + +enum rew { + REW_REWIND, + REW_NOHALT, + REW_HALT +}; + +static int blk_close(MACRO_PROT_ARGS); +static int blk_exp(MACRO_PROT_ARGS); +static int blk_imp(MACRO_PROT_ARGS); +static int in_line_eoln(MACRO_PROT_ARGS); +static int man_args(struct man *, int, + int *, char *, char **); + +static int rew_scope(enum man_type, + struct man *, enum mant); +static enum rew rew_dohalt(enum mant, enum man_type, + const struct man_node *); +static enum rew rew_block(enum mant, enum man_type, + const struct man_node *); +static void rew_warn(struct man *, + struct man_node *, enum mandocerr); + +const struct man_macro __man_macros[MAN_MAX] = { + { in_line_eoln, MAN_NSCOPED }, /* br */ + { in_line_eoln, MAN_BSCOPE }, /* TH */ + { blk_imp, MAN_BSCOPE | MAN_SCOPED }, /* SH */ + { blk_imp, MAN_BSCOPE | MAN_SCOPED }, /* SS */ + { blk_imp, MAN_BSCOPE | MAN_SCOPED | MAN_FSCOPED }, /* TP */ + { blk_imp, MAN_BSCOPE }, /* LP */ + { blk_imp, MAN_BSCOPE }, /* PP */ + { blk_imp, MAN_BSCOPE }, /* P */ + { blk_imp, MAN_BSCOPE }, /* IP */ + { blk_imp, MAN_BSCOPE }, /* HP */ + { in_line_eoln, MAN_SCOPED }, /* SM */ + { in_line_eoln, MAN_SCOPED }, /* SB */ + { in_line_eoln, 0 }, /* BI */ + { in_line_eoln, 0 }, /* IB */ + { in_line_eoln, 0 }, /* BR */ + { in_line_eoln, 0 }, /* RB */ + { in_line_eoln, MAN_SCOPED }, /* R */ + { in_line_eoln, MAN_SCOPED }, /* B */ + { in_line_eoln, MAN_SCOPED }, /* I */ + { in_line_eoln, 0 }, /* IR */ + { in_line_eoln, 0 }, /* RI */ + { in_line_eoln, MAN_NSCOPED }, /* na */ + { in_line_eoln, MAN_NSCOPED }, /* sp */ + { in_line_eoln, MAN_BSCOPE }, /* nf */ + { in_line_eoln, MAN_BSCOPE }, /* fi */ + { blk_close, 0 }, /* RE */ + { blk_exp, MAN_EXPLICIT }, /* RS */ + { in_line_eoln, 0 }, /* DT */ + { in_line_eoln, 0 }, /* UC */ + { in_line_eoln, 0 }, /* PD */ + { in_line_eoln, 0 }, /* AT */ + { in_line_eoln, 0 }, /* in */ + { in_line_eoln, 0 }, /* ft */ + { in_line_eoln, 0 }, /* OP */ +}; + +const struct man_macro * const man_macros = __man_macros; + + +/* + * Warn when "n" is an explicit non-roff macro. + */ +static void +rew_warn(struct man *m, struct man_node *n, enum mandocerr er) +{ + + if (er == MANDOCERR_MAX || MAN_BLOCK != n->type) + return; + if (MAN_VALID & n->flags) + return; + if ( ! (MAN_EXPLICIT & man_macros[n->tok].flags)) + return; + + assert(er < MANDOCERR_FATAL); + man_nmsg(m, n, er); +} + + +/* + * Rewind scope. If a code "er" != MANDOCERR_MAX has been provided, it + * will be used if an explicit block scope is being closed out. + */ +int +man_unscope(struct man *m, const struct man_node *to, + enum mandocerr er) +{ + struct man_node *n; + + assert(to); + + m->next = MAN_NEXT_SIBLING; + + /* LINTED */ + while (m->last != to) { + /* + * Save the parent here, because we may delete the + * m->last node in the post-validation phase and reset + * it to m->last->parent, causing a step in the closing + * out to be lost. + */ + n = m->last->parent; + rew_warn(m, m->last, er); + if ( ! man_valid_post(m)) + return(0); + m->last = n; + assert(m->last); + } + + rew_warn(m, m->last, er); + if ( ! man_valid_post(m)) + return(0); + + return(1); +} + + +static enum rew +rew_block(enum mant ntok, enum man_type type, const struct man_node *n) +{ + + if (MAN_BLOCK == type && ntok == n->parent->tok && + MAN_BODY == n->parent->type) + return(REW_REWIND); + return(ntok == n->tok ? REW_HALT : REW_NOHALT); +} + + +/* + * There are three scope levels: scoped to the root (all), scoped to the + * section (all less sections), and scoped to subsections (all less + * sections and subsections). + */ +static enum rew +rew_dohalt(enum mant tok, enum man_type type, const struct man_node *n) +{ + enum rew c; + + /* We cannot progress beyond the root ever. */ + if (MAN_ROOT == n->type) + return(REW_HALT); + + assert(n->parent); + + /* Normal nodes shouldn't go to the level of the root. */ + if (MAN_ROOT == n->parent->type) + return(REW_REWIND); + + /* Already-validated nodes should be closed out. */ + if (MAN_VALID & n->flags) + return(REW_NOHALT); + + /* First: rewind to ourselves. */ + if (type == n->type && tok == n->tok) + return(REW_REWIND); + + /* + * Next follow the implicit scope-smashings as defined by man.7: + * section, sub-section, etc. + */ + + switch (tok) { + case (MAN_SH): + break; + case (MAN_SS): + /* Rewind to a section, if a block. */ + if (REW_NOHALT != (c = rew_block(MAN_SH, type, n))) + return(c); + break; + case (MAN_RS): + /* Rewind to a subsection, if a block. */ + if (REW_NOHALT != (c = rew_block(MAN_SS, type, n))) + return(c); + /* Rewind to a section, if a block. */ + if (REW_NOHALT != (c = rew_block(MAN_SH, type, n))) + return(c); + break; + default: + /* Rewind to an offsetter, if a block. */ + if (REW_NOHALT != (c = rew_block(MAN_RS, type, n))) + return(c); + /* Rewind to a subsection, if a block. */ + if (REW_NOHALT != (c = rew_block(MAN_SS, type, n))) + return(c); + /* Rewind to a section, if a block. */ + if (REW_NOHALT != (c = rew_block(MAN_SH, type, n))) + return(c); + break; + } + + return(REW_NOHALT); +} + + +/* + * Rewinding entails ascending the parse tree until a coherent point, + * for example, the `SH' macro will close out any intervening `SS' + * scopes. When a scope is closed, it must be validated and actioned. + */ +static int +rew_scope(enum man_type type, struct man *m, enum mant tok) +{ + struct man_node *n; + enum rew c; + + /* LINTED */ + for (n = m->last; n; n = n->parent) { + /* + * Whether we should stop immediately (REW_HALT), stop + * and rewind until this point (REW_REWIND), or keep + * rewinding (REW_NOHALT). + */ + c = rew_dohalt(tok, type, n); + if (REW_HALT == c) + return(1); + if (REW_REWIND == c) + break; + } + + /* + * Rewind until the current point. Warn if we're a roff + * instruction that's mowing over explicit scopes. + */ + assert(n); + + return(man_unscope(m, n, MANDOCERR_MAX)); +} + + +/* + * Close out a generic explicit macro. + */ +/* ARGSUSED */ +int +blk_close(MACRO_PROT_ARGS) +{ + enum mant ntok; + const struct man_node *nn; + + switch (tok) { + case (MAN_RE): + ntok = MAN_RS; + break; + default: + abort(); + /* NOTREACHED */ + } + + for (nn = m->last->parent; nn; nn = nn->parent) + if (ntok == nn->tok) + break; + + if (NULL == nn) + man_pmsg(m, line, ppos, MANDOCERR_NOSCOPE); + + if ( ! rew_scope(MAN_BODY, m, ntok)) + return(0); + if ( ! rew_scope(MAN_BLOCK, m, ntok)) + return(0); + + return(1); +} + + +/* ARGSUSED */ +int +blk_exp(MACRO_PROT_ARGS) +{ + int la; + char *p; + + /* + * Close out prior scopes. "Regular" explicit macros cannot be + * nested, but we allow roff macros to be placed just about + * anywhere. + */ + + if ( ! man_block_alloc(m, line, ppos, tok)) + return(0); + if ( ! man_head_alloc(m, line, ppos, tok)) + return(0); + + for (;;) { + la = *pos; + if ( ! man_args(m, line, pos, buf, &p)) + break; + if ( ! man_word_alloc(m, line, la, p)) + return(0); + } + + assert(m); + assert(tok != MAN_MAX); + + if ( ! rew_scope(MAN_HEAD, m, tok)) + return(0); + return(man_body_alloc(m, line, ppos, tok)); +} + + + +/* + * Parse an implicit-block macro. These contain a MAN_HEAD and a + * MAN_BODY contained within a MAN_BLOCK. Rules for closing out other + * scopes, such as `SH' closing out an `SS', are defined in the rew + * routines. + */ +/* ARGSUSED */ +int +blk_imp(MACRO_PROT_ARGS) +{ + int la; + char *p; + struct man_node *n; + + /* Close out prior scopes. */ + + if ( ! rew_scope(MAN_BODY, m, tok)) + return(0); + if ( ! rew_scope(MAN_BLOCK, m, tok)) + return(0); + + /* Allocate new block & head scope. */ + + if ( ! man_block_alloc(m, line, ppos, tok)) + return(0); + if ( ! man_head_alloc(m, line, ppos, tok)) + return(0); + + n = m->last; + + /* Add line arguments. */ + + for (;;) { + la = *pos; + if ( ! man_args(m, line, pos, buf, &p)) + break; + if ( ! man_word_alloc(m, line, la, p)) + return(0); + } + + /* Close out head and open body (unless MAN_SCOPE). */ + + if (MAN_SCOPED & man_macros[tok].flags) { + /* If we're forcing scope (`TP'), keep it open. */ + if (MAN_FSCOPED & man_macros[tok].flags) { + m->flags |= MAN_BLINE; + return(1); + } else if (n == m->last) { + m->flags |= MAN_BLINE; + return(1); + } + } + + if ( ! rew_scope(MAN_HEAD, m, tok)) + return(0); + return(man_body_alloc(m, line, ppos, tok)); +} + + +/* ARGSUSED */ +int +in_line_eoln(MACRO_PROT_ARGS) +{ + int la; + char *p; + struct man_node *n; + + if ( ! man_elem_alloc(m, line, ppos, tok)) + return(0); + + n = m->last; + + for (;;) { + la = *pos; + if ( ! man_args(m, line, pos, buf, &p)) + break; + if ( ! man_word_alloc(m, line, la, p)) + return(0); + } + + /* + * If no arguments are specified and this is MAN_SCOPED (i.e., + * next-line scoped), then set our mode to indicate that we're + * waiting for terms to load into our context. + */ + + if (n == m->last && MAN_SCOPED & man_macros[tok].flags) { + assert( ! (MAN_NSCOPED & man_macros[tok].flags)); + m->flags |= MAN_ELINE; + return(1); + } + + /* Set ignorable context, if applicable. */ + + if (MAN_NSCOPED & man_macros[tok].flags) { + assert( ! (MAN_SCOPED & man_macros[tok].flags)); + m->flags |= MAN_ILINE; + } + + assert(MAN_ROOT != m->last->type); + m->next = MAN_NEXT_SIBLING; + + /* + * Rewind our element scope. Note that when TH is pruned, we'll + * be back at the root, so make sure that we don't clobber as + * its sibling. + */ + + for ( ; m->last; m->last = m->last->parent) { + if (m->last == n) + break; + if (m->last->type == MAN_ROOT) + break; + if ( ! man_valid_post(m)) + return(0); + } + + assert(m->last); + + /* + * Same here regarding whether we're back at the root. + */ + + if (m->last->type != MAN_ROOT && ! man_valid_post(m)) + return(0); + + return(1); +} + + +int +man_macroend(struct man *m) +{ + + return(man_unscope(m, m->first, MANDOCERR_SCOPEEXIT)); +} + +static int +man_args(struct man *m, int line, int *pos, char *buf, char **v) +{ + char *start; + + assert(*pos); + *v = start = buf + *pos; + assert(' ' != *start); + + if ('\0' == *start) + return(0); + + *v = mandoc_getarg(m->parse, v, line, pos); + return(1); +} diff --git a/man_term.c b/man_term.c new file mode 100644 index 000000000000..69c5c95e442d --- /dev/null +++ b/man_term.c @@ -0,0 +1,1117 @@ +/* $Id: man_term.c,v 1.127 2012/01/03 15:16:24 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * Copyright (c) 2010, 2011 Ingo Schwarze <schwarze@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/types.h> + +#include <assert.h> +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "mandoc.h" +#include "out.h" +#include "man.h" +#include "term.h" +#include "main.h" + +#define MAXMARGINS 64 /* maximum number of indented scopes */ + +/* FIXME: have PD set the default vspace width. */ + +struct mtermp { + int fl; +#define MANT_LITERAL (1 << 0) + size_t lmargin[MAXMARGINS]; /* margins (incl. visible page) */ + int lmargincur; /* index of current margin */ + int lmarginsz; /* actual number of nested margins */ + size_t offset; /* default offset to visible page */ +}; + +#define DECL_ARGS struct termp *p, \ + struct mtermp *mt, \ + const struct man_node *n, \ + const struct man_meta *m + +struct termact { + int (*pre)(DECL_ARGS); + void (*post)(DECL_ARGS); + int flags; +#define MAN_NOTEXT (1 << 0) /* Never has text children. */ +}; + +static int a2width(const struct termp *, const char *); +static size_t a2height(const struct termp *, const char *); + +static void print_man_nodelist(DECL_ARGS); +static void print_man_node(DECL_ARGS); +static void print_man_head(struct termp *, const void *); +static void print_man_foot(struct termp *, const void *); +static void print_bvspace(struct termp *, + const struct man_node *); + +static int pre_B(DECL_ARGS); +static int pre_HP(DECL_ARGS); +static int pre_I(DECL_ARGS); +static int pre_IP(DECL_ARGS); +static int pre_OP(DECL_ARGS); +static int pre_PP(DECL_ARGS); +static int pre_RS(DECL_ARGS); +static int pre_SH(DECL_ARGS); +static int pre_SS(DECL_ARGS); +static int pre_TP(DECL_ARGS); +static int pre_alternate(DECL_ARGS); +static int pre_ft(DECL_ARGS); +static int pre_ign(DECL_ARGS); +static int pre_in(DECL_ARGS); +static int pre_literal(DECL_ARGS); +static int pre_sp(DECL_ARGS); + +static void post_IP(DECL_ARGS); +static void post_HP(DECL_ARGS); +static void post_RS(DECL_ARGS); +static void post_SH(DECL_ARGS); +static void post_SS(DECL_ARGS); +static void post_TP(DECL_ARGS); + +static const struct termact termacts[MAN_MAX] = { + { pre_sp, NULL, MAN_NOTEXT }, /* br */ + { NULL, NULL, 0 }, /* TH */ + { pre_SH, post_SH, 0 }, /* SH */ + { pre_SS, post_SS, 0 }, /* SS */ + { pre_TP, post_TP, 0 }, /* TP */ + { pre_PP, NULL, 0 }, /* LP */ + { pre_PP, NULL, 0 }, /* PP */ + { pre_PP, NULL, 0 }, /* P */ + { pre_IP, post_IP, 0 }, /* IP */ + { pre_HP, post_HP, 0 }, /* HP */ + { NULL, NULL, 0 }, /* SM */ + { pre_B, NULL, 0 }, /* SB */ + { pre_alternate, NULL, 0 }, /* BI */ + { pre_alternate, NULL, 0 }, /* IB */ + { pre_alternate, NULL, 0 }, /* BR */ + { pre_alternate, NULL, 0 }, /* RB */ + { NULL, NULL, 0 }, /* R */ + { pre_B, NULL, 0 }, /* B */ + { pre_I, NULL, 0 }, /* I */ + { pre_alternate, NULL, 0 }, /* IR */ + { pre_alternate, NULL, 0 }, /* RI */ + { pre_ign, NULL, MAN_NOTEXT }, /* na */ + { pre_sp, NULL, MAN_NOTEXT }, /* sp */ + { pre_literal, NULL, 0 }, /* nf */ + { pre_literal, NULL, 0 }, /* fi */ + { NULL, NULL, 0 }, /* RE */ + { pre_RS, post_RS, 0 }, /* RS */ + { pre_ign, NULL, 0 }, /* DT */ + { pre_ign, NULL, 0 }, /* UC */ + { pre_ign, NULL, 0 }, /* PD */ + { pre_ign, NULL, 0 }, /* AT */ + { pre_in, NULL, MAN_NOTEXT }, /* in */ + { pre_ft, NULL, MAN_NOTEXT }, /* ft */ + { pre_OP, NULL, 0 }, /* OP */ +}; + + + +void +terminal_man(void *arg, const struct man *man) +{ + struct termp *p; + const struct man_node *n; + const struct man_meta *m; + struct mtermp mt; + + p = (struct termp *)arg; + + if (0 == p->defindent) + p->defindent = 7; + + p->overstep = 0; + p->maxrmargin = p->defrmargin; + p->tabwidth = term_len(p, 5); + + if (NULL == p->symtab) + p->symtab = mchars_alloc(); + + n = man_node(man); + m = man_meta(man); + + term_begin(p, print_man_head, print_man_foot, m); + p->flags |= TERMP_NOSPACE; + + memset(&mt, 0, sizeof(struct mtermp)); + + mt.lmargin[mt.lmargincur] = term_len(p, p->defindent); + mt.offset = term_len(p, p->defindent); + + if (n->child) + print_man_nodelist(p, &mt, n->child, m); + + term_end(p); +} + + +static size_t +a2height(const struct termp *p, const char *cp) +{ + struct roffsu su; + + if ( ! a2roffsu(cp, &su, SCALE_VS)) + SCALE_VS_INIT(&su, atoi(cp)); + + return(term_vspan(p, &su)); +} + + +static int +a2width(const struct termp *p, const char *cp) +{ + struct roffsu su; + + if ( ! a2roffsu(cp, &su, SCALE_BU)) + return(-1); + + return((int)term_hspan(p, &su)); +} + +/* + * Printing leading vertical space before a block. + * This is used for the paragraph macros. + * The rules are pretty simple, since there's very little nesting going + * on here. Basically, if we're the first within another block (SS/SH), + * then don't emit vertical space. If we are (RS), then do. If not the + * first, print it. + */ +static void +print_bvspace(struct termp *p, const struct man_node *n) +{ + + term_newln(p); + + if (n->body && n->body->child) + if (MAN_TBL == n->body->child->type) + return; + + if (MAN_ROOT == n->parent->type || MAN_RS != n->parent->tok) + if (NULL == n->prev) + return; + + term_vspace(p); +} + +/* ARGSUSED */ +static int +pre_ign(DECL_ARGS) +{ + + return(0); +} + + +/* ARGSUSED */ +static int +pre_I(DECL_ARGS) +{ + + term_fontrepl(p, TERMFONT_UNDER); + return(1); +} + + +/* ARGSUSED */ +static int +pre_literal(DECL_ARGS) +{ + + term_newln(p); + + if (MAN_nf == n->tok) + mt->fl |= MANT_LITERAL; + else + mt->fl &= ~MANT_LITERAL; + + /* + * Unlike .IP and .TP, .HP does not have a HEAD. + * So in case a second call to term_flushln() is needed, + * indentation has to be set up explicitly. + */ + if (MAN_HP == n->parent->tok && p->rmargin < p->maxrmargin) { + p->offset = p->rmargin; + p->rmargin = p->maxrmargin; + p->flags &= ~(TERMP_NOBREAK | TERMP_TWOSPACE); + p->flags |= TERMP_NOSPACE; + } + + return(0); +} + +/* ARGSUSED */ +static int +pre_alternate(DECL_ARGS) +{ + enum termfont font[2]; + const struct man_node *nn; + int savelit, i; + + switch (n->tok) { + case (MAN_RB): + font[0] = TERMFONT_NONE; + font[1] = TERMFONT_BOLD; + break; + case (MAN_RI): + font[0] = TERMFONT_NONE; + font[1] = TERMFONT_UNDER; + break; + case (MAN_BR): + font[0] = TERMFONT_BOLD; + font[1] = TERMFONT_NONE; + break; + case (MAN_BI): + font[0] = TERMFONT_BOLD; + font[1] = TERMFONT_UNDER; + break; + case (MAN_IR): + font[0] = TERMFONT_UNDER; + font[1] = TERMFONT_NONE; + break; + case (MAN_IB): + font[0] = TERMFONT_UNDER; + font[1] = TERMFONT_BOLD; + break; + default: + abort(); + } + + savelit = MANT_LITERAL & mt->fl; + mt->fl &= ~MANT_LITERAL; + + for (i = 0, nn = n->child; nn; nn = nn->next, i = 1 - i) { + term_fontrepl(p, font[i]); + if (savelit && NULL == nn->next) + mt->fl |= MANT_LITERAL; + print_man_node(p, mt, nn, m); + if (nn->next) + p->flags |= TERMP_NOSPACE; + } + + return(0); +} + +/* ARGSUSED */ +static int +pre_B(DECL_ARGS) +{ + + term_fontrepl(p, TERMFONT_BOLD); + return(1); +} + +/* ARGSUSED */ +static int +pre_OP(DECL_ARGS) +{ + + term_word(p, "["); + p->flags |= TERMP_NOSPACE; + + if (NULL != (n = n->child)) { + term_fontrepl(p, TERMFONT_BOLD); + term_word(p, n->string); + } + if (NULL != n && NULL != n->next) { + term_fontrepl(p, TERMFONT_UNDER); + term_word(p, n->next->string); + } + + term_fontrepl(p, TERMFONT_NONE); + p->flags |= TERMP_NOSPACE; + term_word(p, "]"); + return(0); +} + +/* ARGSUSED */ +static int +pre_ft(DECL_ARGS) +{ + const char *cp; + + if (NULL == n->child) { + term_fontlast(p); + return(0); + } + + cp = n->child->string; + switch (*cp) { + case ('4'): + /* FALLTHROUGH */ + case ('3'): + /* FALLTHROUGH */ + case ('B'): + term_fontrepl(p, TERMFONT_BOLD); + break; + case ('2'): + /* FALLTHROUGH */ + case ('I'): + term_fontrepl(p, TERMFONT_UNDER); + break; + case ('P'): + term_fontlast(p); + break; + case ('1'): + /* FALLTHROUGH */ + case ('C'): + /* FALLTHROUGH */ + case ('R'): + term_fontrepl(p, TERMFONT_NONE); + break; + default: + break; + } + return(0); +} + +/* ARGSUSED */ +static int +pre_in(DECL_ARGS) +{ + int len, less; + size_t v; + const char *cp; + + term_newln(p); + + if (NULL == n->child) { + p->offset = mt->offset; + return(0); + } + + cp = n->child->string; + less = 0; + + if ('-' == *cp) + less = -1; + else if ('+' == *cp) + less = 1; + else + cp--; + + if ((len = a2width(p, ++cp)) < 0) + return(0); + + v = (size_t)len; + + if (less < 0) + p->offset -= p->offset > v ? v : p->offset; + else if (less > 0) + p->offset += v; + else + p->offset = v; + + /* Don't let this creep beyond the right margin. */ + + if (p->offset > p->rmargin) + p->offset = p->rmargin; + + return(0); +} + + +/* ARGSUSED */ +static int +pre_sp(DECL_ARGS) +{ + size_t i, len; + + if ((NULL == n->prev && n->parent)) { + if (MAN_SS == n->parent->tok) + return(0); + if (MAN_SH == n->parent->tok) + return(0); + } + + switch (n->tok) { + case (MAN_br): + len = 0; + break; + default: + len = n->child ? a2height(p, n->child->string) : 1; + break; + } + + if (0 == len) + term_newln(p); + for (i = 0; i < len; i++) + term_vspace(p); + + return(0); +} + + +/* ARGSUSED */ +static int +pre_HP(DECL_ARGS) +{ + size_t len, one; + int ival; + const struct man_node *nn; + + switch (n->type) { + case (MAN_BLOCK): + print_bvspace(p, n); + return(1); + case (MAN_BODY): + p->flags |= TERMP_NOBREAK; + p->flags |= TERMP_TWOSPACE; + break; + default: + return(0); + } + + len = mt->lmargin[mt->lmargincur]; + ival = -1; + + /* Calculate offset. */ + + if (NULL != (nn = n->parent->head->child)) + if ((ival = a2width(p, nn->string)) >= 0) + len = (size_t)ival; + + one = term_len(p, 1); + if (len < one) + len = one; + + p->offset = mt->offset; + p->rmargin = mt->offset + len; + + if (ival >= 0) + mt->lmargin[mt->lmargincur] = (size_t)ival; + + return(1); +} + + +/* ARGSUSED */ +static void +post_HP(DECL_ARGS) +{ + + switch (n->type) { + case (MAN_BLOCK): + term_flushln(p); + break; + case (MAN_BODY): + term_flushln(p); + p->flags &= ~TERMP_NOBREAK; + p->flags &= ~TERMP_TWOSPACE; + p->offset = mt->offset; + p->rmargin = p->maxrmargin; + break; + default: + break; + } +} + + +/* ARGSUSED */ +static int +pre_PP(DECL_ARGS) +{ + + switch (n->type) { + case (MAN_BLOCK): + mt->lmargin[mt->lmargincur] = term_len(p, p->defindent); + print_bvspace(p, n); + break; + default: + p->offset = mt->offset; + break; + } + + return(MAN_HEAD != n->type); +} + + +/* ARGSUSED */ +static int +pre_IP(DECL_ARGS) +{ + const struct man_node *nn; + size_t len; + int savelit, ival; + + switch (n->type) { + case (MAN_BODY): + p->flags |= TERMP_NOSPACE; + break; + case (MAN_HEAD): + p->flags |= TERMP_NOBREAK; + break; + case (MAN_BLOCK): + print_bvspace(p, n); + /* FALLTHROUGH */ + default: + return(1); + } + + len = mt->lmargin[mt->lmargincur]; + ival = -1; + + /* Calculate the offset from the optional second argument. */ + if (NULL != (nn = n->parent->head->child)) + if (NULL != (nn = nn->next)) + if ((ival = a2width(p, nn->string)) >= 0) + len = (size_t)ival; + + switch (n->type) { + case (MAN_HEAD): + /* Handle zero-width lengths. */ + if (0 == len) + len = term_len(p, 1); + + p->offset = mt->offset; + p->rmargin = mt->offset + len; + if (ival < 0) + break; + + /* Set the saved left-margin. */ + mt->lmargin[mt->lmargincur] = (size_t)ival; + + savelit = MANT_LITERAL & mt->fl; + mt->fl &= ~MANT_LITERAL; + + if (n->child) + print_man_node(p, mt, n->child, m); + + if (savelit) + mt->fl |= MANT_LITERAL; + + return(0); + case (MAN_BODY): + p->offset = mt->offset + len; + p->rmargin = p->maxrmargin; + break; + default: + break; + } + + return(1); +} + + +/* ARGSUSED */ +static void +post_IP(DECL_ARGS) +{ + + switch (n->type) { + case (MAN_HEAD): + term_flushln(p); + p->flags &= ~TERMP_NOBREAK; + p->rmargin = p->maxrmargin; + break; + case (MAN_BODY): + term_newln(p); + break; + default: + break; + } +} + + +/* ARGSUSED */ +static int +pre_TP(DECL_ARGS) +{ + const struct man_node *nn; + size_t len; + int savelit, ival; + + switch (n->type) { + case (MAN_HEAD): + p->flags |= TERMP_NOBREAK; + break; + case (MAN_BODY): + p->flags |= TERMP_NOSPACE; + break; + case (MAN_BLOCK): + print_bvspace(p, n); + /* FALLTHROUGH */ + default: + return(1); + } + + len = (size_t)mt->lmargin[mt->lmargincur]; + ival = -1; + + /* Calculate offset. */ + + if (NULL != (nn = n->parent->head->child)) + if (nn->string && nn->parent->line == nn->line) + if ((ival = a2width(p, nn->string)) >= 0) + len = (size_t)ival; + + switch (n->type) { + case (MAN_HEAD): + /* Handle zero-length properly. */ + if (0 == len) + len = term_len(p, 1); + + p->offset = mt->offset; + p->rmargin = mt->offset + len; + + savelit = MANT_LITERAL & mt->fl; + mt->fl &= ~MANT_LITERAL; + + /* Don't print same-line elements. */ + for (nn = n->child; nn; nn = nn->next) + if (nn->line > n->line) + print_man_node(p, mt, nn, m); + + if (savelit) + mt->fl |= MANT_LITERAL; + if (ival >= 0) + mt->lmargin[mt->lmargincur] = (size_t)ival; + + return(0); + case (MAN_BODY): + p->offset = mt->offset + len; + p->rmargin = p->maxrmargin; + break; + default: + break; + } + + return(1); +} + + +/* ARGSUSED */ +static void +post_TP(DECL_ARGS) +{ + + switch (n->type) { + case (MAN_HEAD): + term_flushln(p); + p->flags &= ~TERMP_NOBREAK; + p->flags &= ~TERMP_TWOSPACE; + p->rmargin = p->maxrmargin; + break; + case (MAN_BODY): + term_newln(p); + break; + default: + break; + } +} + + +/* ARGSUSED */ +static int +pre_SS(DECL_ARGS) +{ + + switch (n->type) { + case (MAN_BLOCK): + mt->fl &= ~MANT_LITERAL; + mt->lmargin[mt->lmargincur] = term_len(p, p->defindent); + mt->offset = term_len(p, p->defindent); + /* If following a prior empty `SS', no vspace. */ + if (n->prev && MAN_SS == n->prev->tok) + if (NULL == n->prev->body->child) + break; + if (NULL == n->prev) + break; + term_vspace(p); + break; + case (MAN_HEAD): + term_fontrepl(p, TERMFONT_BOLD); + p->offset = term_len(p, p->defindent/2); + break; + case (MAN_BODY): + p->offset = mt->offset; + break; + default: + break; + } + + return(1); +} + + +/* ARGSUSED */ +static void +post_SS(DECL_ARGS) +{ + + switch (n->type) { + case (MAN_HEAD): + term_newln(p); + break; + case (MAN_BODY): + term_newln(p); + break; + default: + break; + } +} + + +/* ARGSUSED */ +static int +pre_SH(DECL_ARGS) +{ + + switch (n->type) { + case (MAN_BLOCK): + mt->fl &= ~MANT_LITERAL; + mt->lmargin[mt->lmargincur] = term_len(p, p->defindent); + mt->offset = term_len(p, p->defindent); + /* If following a prior empty `SH', no vspace. */ + if (n->prev && MAN_SH == n->prev->tok) + if (NULL == n->prev->body->child) + break; + /* If the first macro, no vspae. */ + if (NULL == n->prev) + break; + term_vspace(p); + break; + case (MAN_HEAD): + term_fontrepl(p, TERMFONT_BOLD); + p->offset = 0; + break; + case (MAN_BODY): + p->offset = mt->offset; + break; + default: + break; + } + + return(1); +} + + +/* ARGSUSED */ +static void +post_SH(DECL_ARGS) +{ + + switch (n->type) { + case (MAN_HEAD): + term_newln(p); + break; + case (MAN_BODY): + term_newln(p); + break; + default: + break; + } +} + +/* ARGSUSED */ +static int +pre_RS(DECL_ARGS) +{ + int ival; + size_t sz; + + switch (n->type) { + case (MAN_BLOCK): + term_newln(p); + return(1); + case (MAN_HEAD): + return(0); + default: + break; + } + + sz = term_len(p, p->defindent); + + if (NULL != (n = n->parent->head->child)) + if ((ival = a2width(p, n->string)) >= 0) + sz = (size_t)ival; + + mt->offset += sz; + p->rmargin = p->maxrmargin; + p->offset = mt->offset < p->rmargin ? mt->offset : p->rmargin; + + if (++mt->lmarginsz < MAXMARGINS) + mt->lmargincur = mt->lmarginsz; + + mt->lmargin[mt->lmargincur] = mt->lmargin[mt->lmargincur - 1]; + return(1); +} + +/* ARGSUSED */ +static void +post_RS(DECL_ARGS) +{ + int ival; + size_t sz; + + switch (n->type) { + case (MAN_BLOCK): + return; + case (MAN_HEAD): + return; + default: + term_newln(p); + break; + } + + sz = term_len(p, p->defindent); + + if (NULL != (n = n->parent->head->child)) + if ((ival = a2width(p, n->string)) >= 0) + sz = (size_t)ival; + + mt->offset = mt->offset < sz ? 0 : mt->offset - sz; + p->offset = mt->offset; + + if (--mt->lmarginsz < MAXMARGINS) + mt->lmargincur = mt->lmarginsz; +} + +static void +print_man_node(DECL_ARGS) +{ + size_t rm, rmax; + int c; + + switch (n->type) { + case(MAN_TEXT): + /* + * If we have a blank line, output a vertical space. + * If we have a space as the first character, break + * before printing the line's data. + */ + if ('\0' == *n->string) { + term_vspace(p); + return; + } else if (' ' == *n->string && MAN_LINE & n->flags) + term_newln(p); + + term_word(p, n->string); + + /* + * If we're in a literal context, make sure that words + * togehter on the same line stay together. This is a + * POST-printing call, so we check the NEXT word. Since + * -man doesn't have nested macros, we don't need to be + * more specific than this. + */ + if (MANT_LITERAL & mt->fl && ! (TERMP_NOBREAK & p->flags) && + (NULL == n->next || + n->next->line > n->line)) { + rm = p->rmargin; + rmax = p->maxrmargin; + p->rmargin = p->maxrmargin = TERM_MAXMARGIN; + p->flags |= TERMP_NOSPACE; + term_flushln(p); + p->rmargin = rm; + p->maxrmargin = rmax; + } + + if (MAN_EOS & n->flags) + p->flags |= TERMP_SENTENCE; + return; + case (MAN_EQN): + term_eqn(p, n->eqn); + return; + case (MAN_TBL): + /* + * Tables are preceded by a newline. Then process a + * table line, which will cause line termination, + */ + if (TBL_SPAN_FIRST & n->span->flags) + term_newln(p); + term_tbl(p, n->span); + return; + default: + break; + } + + if ( ! (MAN_NOTEXT & termacts[n->tok].flags)) + term_fontrepl(p, TERMFONT_NONE); + + c = 1; + if (termacts[n->tok].pre) + c = (*termacts[n->tok].pre)(p, mt, n, m); + + if (c && n->child) + print_man_nodelist(p, mt, n->child, m); + + if (termacts[n->tok].post) + (*termacts[n->tok].post)(p, mt, n, m); + if ( ! (MAN_NOTEXT & termacts[n->tok].flags)) + term_fontrepl(p, TERMFONT_NONE); + + if (MAN_EOS & n->flags) + p->flags |= TERMP_SENTENCE; +} + + +static void +print_man_nodelist(DECL_ARGS) +{ + + print_man_node(p, mt, n, m); + if ( ! n->next) + return; + print_man_nodelist(p, mt, n->next, m); +} + + +static void +print_man_foot(struct termp *p, const void *arg) +{ + char title[BUFSIZ]; + size_t datelen; + const struct man_meta *meta; + + meta = (const struct man_meta *)arg; + assert(meta->title); + assert(meta->msec); + assert(meta->date); + + term_fontrepl(p, TERMFONT_NONE); + + term_vspace(p); + + /* + * Temporary, undocumented option to imitate mdoc(7) output. + * In the bottom right corner, use the source instead of + * the title. + */ + + if ( ! p->mdocstyle) { + term_vspace(p); + term_vspace(p); + snprintf(title, BUFSIZ, "%s(%s)", meta->title, meta->msec); + } else if (meta->source) { + strlcpy(title, meta->source, BUFSIZ); + } else { + title[0] = '\0'; + } + datelen = term_strlen(p, meta->date); + + /* Bottom left corner: manual source. */ + + p->flags |= TERMP_NOSPACE | TERMP_NOBREAK; + p->offset = 0; + p->rmargin = (p->maxrmargin - datelen + term_len(p, 1)) / 2; + + if (meta->source) + term_word(p, meta->source); + term_flushln(p); + + /* At the bottom in the middle: manual date. */ + + p->flags |= TERMP_NOSPACE; + p->offset = p->rmargin; + p->rmargin = p->maxrmargin - term_strlen(p, title); + if (p->offset + datelen >= p->rmargin) + p->rmargin = p->offset + datelen; + + term_word(p, meta->date); + term_flushln(p); + + /* Bottom right corner: manual title and section. */ + + p->flags &= ~TERMP_NOBREAK; + p->flags |= TERMP_NOSPACE; + p->offset = p->rmargin; + p->rmargin = p->maxrmargin; + + term_word(p, title); + term_flushln(p); +} + + +static void +print_man_head(struct termp *p, const void *arg) +{ + char buf[BUFSIZ], title[BUFSIZ]; + size_t buflen, titlen; + const struct man_meta *m; + + m = (const struct man_meta *)arg; + assert(m->title); + assert(m->msec); + + if (m->vol) + strlcpy(buf, m->vol, BUFSIZ); + else + buf[0] = '\0'; + buflen = term_strlen(p, buf); + + /* Top left corner: manual title and section. */ + + snprintf(title, BUFSIZ, "%s(%s)", m->title, m->msec); + titlen = term_strlen(p, title); + + p->flags |= TERMP_NOBREAK | TERMP_NOSPACE; + p->offset = 0; + p->rmargin = 2 * (titlen+1) + buflen < p->maxrmargin ? + (p->maxrmargin - + term_strlen(p, buf) + term_len(p, 1)) / 2 : + p->maxrmargin - buflen; + + term_word(p, title); + term_flushln(p); + + /* At the top in the middle: manual volume. */ + + p->flags |= TERMP_NOSPACE; + p->offset = p->rmargin; + p->rmargin = p->offset + buflen + titlen < p->maxrmargin ? + p->maxrmargin - titlen : p->maxrmargin; + + term_word(p, buf); + term_flushln(p); + + /* Top right corner: title and section, again. */ + + p->flags &= ~TERMP_NOBREAK; + if (p->rmargin + titlen <= p->maxrmargin) { + p->flags |= TERMP_NOSPACE; + p->offset = p->rmargin; + p->rmargin = p->maxrmargin; + term_word(p, title); + term_flushln(p); + } + + p->flags &= ~TERMP_NOSPACE; + p->offset = 0; + p->rmargin = p->maxrmargin; + + /* + * Groff prints three blank lines before the content. + * Do the same, except in the temporary, undocumented + * mode imitating mdoc(7) output. + */ + + term_vspace(p); + if ( ! p->mdocstyle) { + term_vspace(p); + term_vspace(p); + } +} diff --git a/man_validate.c b/man_validate.c new file mode 100644 index 000000000000..e40b089f53b8 --- /dev/null +++ b/man_validate.c @@ -0,0 +1,550 @@ +/* $Id: man_validate.c,v 1.80 2012/01/03 15:16:24 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * Copyright (c) 2010 Ingo Schwarze <schwarze@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/types.h> + +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "man.h" +#include "mandoc.h" +#include "libman.h" +#include "libmandoc.h" + +#define CHKARGS struct man *m, struct man_node *n + +typedef int (*v_check)(CHKARGS); + +struct man_valid { + v_check *pres; + v_check *posts; +}; + +static int check_eq0(CHKARGS); +static int check_eq2(CHKARGS); +static int check_le1(CHKARGS); +static int check_ge2(CHKARGS); +static int check_le5(CHKARGS); +static int check_par(CHKARGS); +static int check_part(CHKARGS); +static int check_root(CHKARGS); +static void check_text(CHKARGS); + +static int post_AT(CHKARGS); +static int post_vs(CHKARGS); +static int post_fi(CHKARGS); +static int post_ft(CHKARGS); +static int post_nf(CHKARGS); +static int post_sec(CHKARGS); +static int post_TH(CHKARGS); +static int post_UC(CHKARGS); +static int pre_sec(CHKARGS); + +static v_check posts_at[] = { post_AT, NULL }; +static v_check posts_br[] = { post_vs, check_eq0, NULL }; +static v_check posts_eq0[] = { check_eq0, NULL }; +static v_check posts_eq2[] = { check_eq2, NULL }; +static v_check posts_fi[] = { check_eq0, post_fi, NULL }; +static v_check posts_ft[] = { post_ft, NULL }; +static v_check posts_nf[] = { check_eq0, post_nf, NULL }; +static v_check posts_par[] = { check_par, NULL }; +static v_check posts_part[] = { check_part, NULL }; +static v_check posts_sec[] = { post_sec, NULL }; +static v_check posts_sp[] = { post_vs, check_le1, NULL }; +static v_check posts_th[] = { check_ge2, check_le5, post_TH, NULL }; +static v_check posts_uc[] = { post_UC, NULL }; +static v_check pres_sec[] = { pre_sec, NULL }; + +static const struct man_valid man_valids[MAN_MAX] = { + { NULL, posts_br }, /* br */ + { NULL, posts_th }, /* TH */ + { pres_sec, posts_sec }, /* SH */ + { pres_sec, posts_sec }, /* SS */ + { NULL, NULL }, /* TP */ + { NULL, posts_par }, /* LP */ + { NULL, posts_par }, /* PP */ + { NULL, posts_par }, /* P */ + { NULL, NULL }, /* IP */ + { NULL, NULL }, /* HP */ + { NULL, NULL }, /* SM */ + { NULL, NULL }, /* SB */ + { NULL, NULL }, /* BI */ + { NULL, NULL }, /* IB */ + { NULL, NULL }, /* BR */ + { NULL, NULL }, /* RB */ + { NULL, NULL }, /* R */ + { NULL, NULL }, /* B */ + { NULL, NULL }, /* I */ + { NULL, NULL }, /* IR */ + { NULL, NULL }, /* RI */ + { NULL, posts_eq0 }, /* na */ + { NULL, posts_sp }, /* sp */ + { NULL, posts_nf }, /* nf */ + { NULL, posts_fi }, /* fi */ + { NULL, NULL }, /* RE */ + { NULL, posts_part }, /* RS */ + { NULL, NULL }, /* DT */ + { NULL, posts_uc }, /* UC */ + { NULL, NULL }, /* PD */ + { NULL, posts_at }, /* AT */ + { NULL, NULL }, /* in */ + { NULL, posts_ft }, /* ft */ + { NULL, posts_eq2 }, /* OP */ +}; + + +int +man_valid_pre(struct man *m, struct man_node *n) +{ + v_check *cp; + + switch (n->type) { + case (MAN_TEXT): + /* FALLTHROUGH */ + case (MAN_ROOT): + /* FALLTHROUGH */ + case (MAN_EQN): + /* FALLTHROUGH */ + case (MAN_TBL): + return(1); + default: + break; + } + + if (NULL == (cp = man_valids[n->tok].pres)) + return(1); + for ( ; *cp; cp++) + if ( ! (*cp)(m, n)) + return(0); + return(1); +} + + +int +man_valid_post(struct man *m) +{ + v_check *cp; + + if (MAN_VALID & m->last->flags) + return(1); + m->last->flags |= MAN_VALID; + + switch (m->last->type) { + case (MAN_TEXT): + check_text(m, m->last); + return(1); + case (MAN_ROOT): + return(check_root(m, m->last)); + case (MAN_EQN): + /* FALLTHROUGH */ + case (MAN_TBL): + return(1); + default: + break; + } + + if (NULL == (cp = man_valids[m->last->tok].posts)) + return(1); + for ( ; *cp; cp++) + if ( ! (*cp)(m, m->last)) + return(0); + + return(1); +} + + +static int +check_root(CHKARGS) +{ + + if (MAN_BLINE & m->flags) + man_nmsg(m, n, MANDOCERR_SCOPEEXIT); + else if (MAN_ELINE & m->flags) + man_nmsg(m, n, MANDOCERR_SCOPEEXIT); + + m->flags &= ~MAN_BLINE; + m->flags &= ~MAN_ELINE; + + if (NULL == m->first->child) { + man_nmsg(m, n, MANDOCERR_NODOCBODY); + return(0); + } else if (NULL == m->meta.title) { + man_nmsg(m, n, MANDOCERR_NOTITLE); + + /* + * If a title hasn't been set, do so now (by + * implication, date and section also aren't set). + */ + + m->meta.title = mandoc_strdup("unknown"); + m->meta.msec = mandoc_strdup("1"); + m->meta.date = mandoc_normdate + (m->parse, NULL, n->line, n->pos); + } + + return(1); +} + +static void +check_text(CHKARGS) +{ + char *cp, *p; + + if (MAN_LITERAL & m->flags) + return; + + cp = n->string; + for (p = cp; NULL != (p = strchr(p, '\t')); p++) + man_pmsg(m, n->line, (int)(p - cp), MANDOCERR_BADTAB); +} + +#define INEQ_DEFINE(x, ineq, name) \ +static int \ +check_##name(CHKARGS) \ +{ \ + if (n->nchild ineq (x)) \ + return(1); \ + mandoc_vmsg(MANDOCERR_ARGCOUNT, m->parse, n->line, n->pos, \ + "line arguments %s %d (have %d)", \ + #ineq, (x), n->nchild); \ + return(1); \ +} + +INEQ_DEFINE(0, ==, eq0) +INEQ_DEFINE(2, ==, eq2) +INEQ_DEFINE(1, <=, le1) +INEQ_DEFINE(2, >=, ge2) +INEQ_DEFINE(5, <=, le5) + +static int +post_ft(CHKARGS) +{ + char *cp; + int ok; + + if (0 == n->nchild) + return(1); + + ok = 0; + cp = n->child->string; + switch (*cp) { + case ('1'): + /* FALLTHROUGH */ + case ('2'): + /* FALLTHROUGH */ + case ('3'): + /* FALLTHROUGH */ + case ('4'): + /* FALLTHROUGH */ + case ('I'): + /* FALLTHROUGH */ + case ('P'): + /* FALLTHROUGH */ + case ('R'): + if ('\0' == cp[1]) + ok = 1; + break; + case ('B'): + if ('\0' == cp[1] || ('I' == cp[1] && '\0' == cp[2])) + ok = 1; + break; + case ('C'): + if ('W' == cp[1] && '\0' == cp[2]) + ok = 1; + break; + default: + break; + } + + if (0 == ok) { + mandoc_vmsg + (MANDOCERR_BADFONT, m->parse, + n->line, n->pos, "%s", cp); + *cp = '\0'; + } + + if (1 < n->nchild) + mandoc_vmsg + (MANDOCERR_ARGCOUNT, m->parse, n->line, + n->pos, "want one child (have %d)", + n->nchild); + + return(1); +} + +static int +pre_sec(CHKARGS) +{ + + if (MAN_BLOCK == n->type) + m->flags &= ~MAN_LITERAL; + return(1); +} + +static int +post_sec(CHKARGS) +{ + + if ( ! (MAN_HEAD == n->type && 0 == n->nchild)) + return(1); + + man_nmsg(m, n, MANDOCERR_SYNTARGCOUNT); + return(0); +} + +static int +check_part(CHKARGS) +{ + + if (MAN_BODY == n->type && 0 == n->nchild) + mandoc_msg(MANDOCERR_ARGCWARN, m->parse, n->line, + n->pos, "want children (have none)"); + + return(1); +} + + +static int +check_par(CHKARGS) +{ + + switch (n->type) { + case (MAN_BLOCK): + if (0 == n->body->nchild) + man_node_delete(m, n); + break; + case (MAN_BODY): + if (0 == n->nchild) + man_nmsg(m, n, MANDOCERR_IGNPAR); + break; + case (MAN_HEAD): + if (n->nchild) + man_nmsg(m, n, MANDOCERR_ARGSLOST); + break; + default: + break; + } + + return(1); +} + + +static int +post_TH(CHKARGS) +{ + const char *p; + int line, pos; + + if (m->meta.title) + free(m->meta.title); + if (m->meta.vol) + free(m->meta.vol); + if (m->meta.source) + free(m->meta.source); + if (m->meta.msec) + free(m->meta.msec); + if (m->meta.date) + free(m->meta.date); + + line = n->line; + pos = n->pos; + m->meta.title = m->meta.vol = m->meta.date = + m->meta.msec = m->meta.source = NULL; + + /* ->TITLE<- MSEC DATE SOURCE VOL */ + + n = n->child; + if (n && n->string) { + for (p = n->string; '\0' != *p; p++) { + /* Only warn about this once... */ + if (isalpha((unsigned char)*p) && + ! isupper((unsigned char)*p)) { + man_nmsg(m, n, MANDOCERR_UPPERCASE); + break; + } + } + m->meta.title = mandoc_strdup(n->string); + } else + m->meta.title = mandoc_strdup(""); + + /* TITLE ->MSEC<- DATE SOURCE VOL */ + + if (n) + n = n->next; + if (n && n->string) + m->meta.msec = mandoc_strdup(n->string); + else + m->meta.msec = mandoc_strdup(""); + + /* TITLE MSEC ->DATE<- SOURCE VOL */ + + if (n) + n = n->next; + if (n && n->string && '\0' != n->string[0]) { + pos = n->pos; + m->meta.date = mandoc_normdate + (m->parse, n->string, line, pos); + } else + m->meta.date = mandoc_strdup(""); + + /* TITLE MSEC DATE ->SOURCE<- VOL */ + + if (n && (n = n->next)) + m->meta.source = mandoc_strdup(n->string); + + /* TITLE MSEC DATE SOURCE ->VOL<- */ + /* If missing, use the default VOL name for MSEC. */ + + if (n && (n = n->next)) + m->meta.vol = mandoc_strdup(n->string); + else if ('\0' != m->meta.msec[0] && + (NULL != (p = mandoc_a2msec(m->meta.msec)))) + m->meta.vol = mandoc_strdup(p); + + /* + * Remove the `TH' node after we've processed it for our + * meta-data. + */ + man_node_delete(m, m->last); + return(1); +} + +static int +post_nf(CHKARGS) +{ + + if (MAN_LITERAL & m->flags) + man_nmsg(m, n, MANDOCERR_SCOPEREP); + + m->flags |= MAN_LITERAL; + return(1); +} + +static int +post_fi(CHKARGS) +{ + + if ( ! (MAN_LITERAL & m->flags)) + man_nmsg(m, n, MANDOCERR_WNOSCOPE); + + m->flags &= ~MAN_LITERAL; + return(1); +} + +static int +post_UC(CHKARGS) +{ + static const char * const bsd_versions[] = { + "3rd Berkeley Distribution", + "4th Berkeley Distribution", + "4.2 Berkeley Distribution", + "4.3 Berkeley Distribution", + "4.4 Berkeley Distribution", + }; + + const char *p, *s; + + n = n->child; + + if (NULL == n || MAN_TEXT != n->type) + p = bsd_versions[0]; + else { + s = n->string; + if (0 == strcmp(s, "3")) + p = bsd_versions[0]; + else if (0 == strcmp(s, "4")) + p = bsd_versions[1]; + else if (0 == strcmp(s, "5")) + p = bsd_versions[2]; + else if (0 == strcmp(s, "6")) + p = bsd_versions[3]; + else if (0 == strcmp(s, "7")) + p = bsd_versions[4]; + else + p = bsd_versions[0]; + } + + if (m->meta.source) + free(m->meta.source); + + m->meta.source = mandoc_strdup(p); + return(1); +} + +static int +post_AT(CHKARGS) +{ + static const char * const unix_versions[] = { + "7th Edition", + "System III", + "System V", + "System V Release 2", + }; + + const char *p, *s; + struct man_node *nn; + + n = n->child; + + if (NULL == n || MAN_TEXT != n->type) + p = unix_versions[0]; + else { + s = n->string; + if (0 == strcmp(s, "3")) + p = unix_versions[0]; + else if (0 == strcmp(s, "4")) + p = unix_versions[1]; + else if (0 == strcmp(s, "5")) { + nn = n->next; + if (nn && MAN_TEXT == nn->type && nn->string[0]) + p = unix_versions[3]; + else + p = unix_versions[2]; + } else + p = unix_versions[0]; + } + + if (m->meta.source) + free(m->meta.source); + + m->meta.source = mandoc_strdup(p); + return(1); +} + +static int +post_vs(CHKARGS) +{ + + /* + * Don't warn about this because it occurs in pod2man and would + * cause considerable (unfixable) warnage. + */ + if (NULL == n->prev && MAN_ROOT == n->parent->type) + man_node_delete(m, n); + + return(1); +} diff --git a/mandoc.1 b/mandoc.1 new file mode 100644 index 000000000000..dbff0e31caa3 --- /dev/null +++ b/mandoc.1 @@ -0,0 +1,669 @@ +.\" $Id: mandoc.1,v 1.100 2011/12/25 19:35:44 kristaps Exp $ +.\" +.\" Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: December 25 2011 $ +.Dt MANDOC 1 +.Os +.Sh NAME +.Nm mandoc +.Nd format and display UNIX manuals +.Sh SYNOPSIS +.Nm mandoc +.Op Fl V +.Op Fl m Ns Ar format +.Op Fl O Ns Ar option +.Op Fl T Ns Ar output +.Op Fl W Ns Ar level +.Op Ar +.Sh DESCRIPTION +The +.Nm +utility formats +.Ux +manual pages for display. +.Pp +By default, +.Nm +reads +.Xr mdoc 7 +or +.Xr man 7 +text from stdin, implying +.Fl m Ns Cm andoc , +and produces +.Fl T Ns Cm ascii +output. +.Pp +The arguments are as follows: +.Bl -tag -width Ds +.It Fl m Ns Ar format +Input format. +See +.Sx Input Formats +for available formats. +Defaults to +.Fl m Ns Cm andoc . +.It Fl O Ns Ar option +Comma-separated output options. +.It Fl T Ns Ar output +Output format. +See +.Sx Output Formats +for available formats. +Defaults to +.Fl T Ns Cm ascii . +.It Fl V +Print version and exit. +.It Fl W Ns Ar level +Specify the minimum message +.Ar level +to be reported on the standard error output and to affect the exit status. +The +.Ar level +can be +.Cm warning , +.Cm error , +or +.Cm fatal . +The default is +.Fl W Ns Cm fatal ; +.Fl W Ns Cm all +is an alias for +.Fl W Ns Cm warning . +See +.Sx EXIT STATUS +and +.Sx DIAGNOSTICS +for details. +.Pp +The special option +.Fl W Ns Cm stop +tells +.Nm +to exit after parsing a file that causes warnings or errors of at least +the requested level. +No formatted output will be produced from that file. +If both a +.Ar level +and +.Cm stop +are requested, they can be joined with a comma, for example +.Fl W Ns Cm error , Ns Cm stop . +.It Ar file +Read input from zero or more files. +If unspecified, reads from stdin. +If multiple files are specified, +.Nm +will halt with the first failed parse. +.El +.Ss Input Formats +The +.Nm +utility accepts +.Xr mdoc 7 +and +.Xr man 7 +input with +.Fl m Ns Cm doc +and +.Fl m Ns Cm an , +respectively. +The +.Xr mdoc 7 +format is +.Em strongly +recommended; +.Xr man 7 +should only be used for legacy manuals. +.Pp +A third option, +.Fl m Ns Cm andoc , +which is also the default, determines encoding on-the-fly: if the first +non-comment macro is +.Sq \&Dd +or +.Sq \&Dt , +the +.Xr mdoc 7 +parser is used; otherwise, the +.Xr man 7 +parser is used. +.Pp +If multiple +files are specified with +.Fl m Ns Cm andoc , +each has its file-type determined this way. +If multiple files are +specified and +.Fl m Ns Cm doc +or +.Fl m Ns Cm an +is specified, then this format is used exclusively. +.Ss Output Formats +The +.Nm +utility accepts the following +.Fl T +arguments, which correspond to output modes: +.Bl -tag -width "-Tlocale" +.It Fl T Ns Cm ascii +Produce 7-bit ASCII output. +This is the default. +See +.Sx ASCII Output . +.It Fl T Ns Cm html +Produce strict CSS1/HTML-4.01 output. +See +.Sx HTML Output . +.It Fl T Ns Cm lint +Parse only: produce no output. +Implies +.Fl W Ns Cm warning . +.It Fl T Ns Cm locale +Encode output using the current locale. +See +.Sx Locale Output . +.It Fl T Ns Cm man +Produce +.Xr man 7 +format output. +See +.Sx Man Output . +.It Fl T Ns Cm pdf +Produce PDF output. +See +.Sx PDF Output . +.It Fl T Ns Cm ps +Produce PostScript output. +See +.Sx PostScript Output . +.It Fl T Ns Cm tree +Produce an indented parse tree. +.It Fl T Ns Cm utf8 +Encode output in the UTF\-8 multi-byte format. +See +.Sx UTF\-8 Output . +.It Fl T Ns Cm xhtml +Produce strict CSS1/XHTML-1.0 output. +See +.Sx XHTML Output . +.El +.Pp +If multiple input files are specified, these will be processed by the +corresponding filter in-order. +.Ss ASCII Output +Output produced by +.Fl T Ns Cm ascii , +which is the default, is rendered in standard 7-bit ASCII documented in +.Xr ascii 7 . +.Pp +Font styles are applied by using back-spaced encoding such that an +underlined character +.Sq c +is rendered as +.Sq _ Ns \e[bs] Ns c , +where +.Sq \e[bs] +is the back-space character number 8. +Emboldened characters are rendered as +.Sq c Ns \e[bs] Ns c . +.Pp +The special characters documented in +.Xr mandoc_char 7 +are rendered best-effort in an ASCII equivalent. +If no equivalent is found, +.Sq \&? +is used instead. +.Pp +Output width is limited to 78 visible columns unless literal input lines +exceed this limit. +.Pp +The following +.Fl O +arguments are accepted: +.Bl -tag -width Ds +.It Cm indent Ns = Ns Ar indent +The left margin for normal text is set to +.Ar indent +blank characters instead of the default of five for +.Xr mdoc 7 +and seven for +.Xr man 7 . +Increasing this is not recommended; it may result in degraded formatting, +for example overfull lines or ugly line breaks. +.It Cm width Ns = Ns Ar width +The output width is set to +.Ar width , +which will normalise to \(>=60. +.El +.Ss HTML Output +Output produced by +.Fl T Ns Cm html +conforms to HTML-4.01 strict. +.Pp +The +.Pa example.style.css +file documents style-sheet classes available for customising output. +If a style-sheet is not specified with +.Fl O Ns Ar style , +.Fl T Ns Cm html +defaults to simple output readable in any graphical or text-based web +browser. +.Pp +Special characters are rendered in decimal-encoded UTF\-8. +.Pp +The following +.Fl O +arguments are accepted: +.Bl -tag -width Ds +.It Cm fragment +Omit the +.Aq !DOCTYPE +declaration and the +.Aq html , +.Aq head , +and +.Aq body +elements and only emit the subtree below the +.Aq body +element. +The +.Cm style +argument will be ignored. +This is useful when embedding manual content within existing documents. +.It Cm includes Ns = Ns Ar fmt +The string +.Ar fmt , +for example, +.Ar ../src/%I.html , +is used as a template for linked header files (usually via the +.Sq \&In +macro). +Instances of +.Sq \&%I +are replaced with the include filename. +The default is not to present a +hyperlink. +.It Cm man Ns = Ns Ar fmt +The string +.Ar fmt , +for example, +.Ar ../html%S/%N.%S.html , +is used as a template for linked manuals (usually via the +.Sq \&Xr +macro). +Instances of +.Sq \&%N +and +.Sq %S +are replaced with the linked manual's name and section, respectively. +If no section is included, section 1 is assumed. +The default is not to +present a hyperlink. +.It Cm style Ns = Ns Ar style.css +The file +.Ar style.css +is used for an external style-sheet. +This must be a valid absolute or +relative URI. +.El +.Ss Locale Output +Locale-depending output encoding is triggered with +.Fl T Ns Cm locale . +This option is not available on all systems: systems without locale +support, or those whose internal representation is not natively UCS-4, +will fall back to +.Fl T Ns Cm ascii . +See +.Sx ASCII Output +for font style specification and available command-line arguments. +.Ss Man Output +Translate input format into +.Xr man 7 +output format. +This is useful for distributing manual sources to legancy systems +lacking +.Xr mdoc 7 +formatters. +.Pp +If +.Xr mdoc 7 +is passed as input, it is translated into +.Xr man 7 . +If the input format is +.Xr man 7 , +the input is copied to the output, expanding any +.Xr roff 7 +.Sq so +requests. +The parser is also run, and as usual, the +.Fl W +level controls which +.Sx DIAGNOSTICS +are displayed before copying the input to the output. +.Ss PDF Output +PDF-1.1 output may be generated by +.Fl T Ns Cm pdf . +See +.Sx PostScript Output +for +.Fl O +arguments and defaults. +.Ss PostScript Output +PostScript +.Qq Adobe-3.0 +Level-2 pages may be generated by +.Fl T Ns Cm ps . +Output pages default to letter sized and are rendered in the Times font +family, 11-point. +Margins are calculated as 1/9 the page length and width. +Line-height is 1.4m. +.Pp +Special characters are rendered as in +.Sx ASCII Output . +.Pp +The following +.Fl O +arguments are accepted: +.Bl -tag -width Ds +.It Cm paper Ns = Ns Ar name +The paper size +.Ar name +may be one of +.Ar a3 , +.Ar a4 , +.Ar a5 , +.Ar legal , +or +.Ar letter . +You may also manually specify dimensions as +.Ar NNxNN , +width by height in millimetres. +If an unknown value is encountered, +.Ar letter +is used. +.El +.Ss UTF\-8 Output +Use +.Fl T Ns Cm utf8 +to force a UTF\-8 locale. +See +.Sx Locale Output +for details and options. +.Ss XHTML Output +Output produced by +.Fl T Ns Cm xhtml +conforms to XHTML-1.0 strict. +.Pp +See +.Sx HTML Output +for details; beyond generating XHTML tags instead of HTML tags, these +output modes are identical. +.Sh EXIT STATUS +The +.Nm +utility exits with one of the following values, controlled by the message +.Ar level +associated with the +.Fl W +option: +.Pp +.Bl -tag -width Ds -compact +.It 0 +No warnings or errors occurred, or those that did were ignored because +they were lower than the requested +.Ar level . +.It 2 +At least one warning occurred, but no error, and +.Fl W Ns Cm warning +was specified. +.It 3 +At least one parsing error occurred, but no fatal error, and +.Fl W Ns Cm error +or +.Fl W Ns Cm warning +was specified. +.It 4 +A fatal parsing error occurred. +.It 5 +Invalid command line arguments were specified. +No input files have been read. +.It 6 +An operating system error occurred, for example memory exhaustion or an +error accessing input files. +Such errors cause +.Nm +to exit at once, possibly in the middle of parsing or formatting a file. +.El +.Pp +Note that selecting +.Fl T Ns Cm lint +output mode implies +.Fl W Ns Cm warning . +.Sh EXAMPLES +To page manuals to the terminal: +.Pp +.Dl $ mandoc \-Wall,stop mandoc.1 2\*(Gt&1 | less +.Dl $ mandoc mandoc.1 mdoc.3 mdoc.7 | less +.Pp +To produce HTML manuals with +.Ar style.css +as the style-sheet: +.Pp +.Dl $ mandoc \-Thtml -Ostyle=style.css mdoc.7 \*(Gt mdoc.7.html +.Pp +To check over a large set of manuals: +.Pp +.Dl $ mandoc \-Tlint `find /usr/src -name \e*\e.[1-9]` +.Pp +To produce a series of PostScript manuals for A4 paper: +.Pp +.Dl $ mandoc \-Tps \-Opaper=a4 mdoc.7 man.7 \*(Gt manuals.ps +.Pp +Convert a modern +.Xr mdoc 7 +manual to the older +.Xr man 7 +format, for use on systems lacking an +.Xr mdoc 7 +parser: +.Pp +.Dl $ mandoc \-Tman foo.mdoc \*(Gt foo.man +.Sh DIAGNOSTICS +Standard error messages reporting parsing errors are prefixed by +.Pp +.Sm off +.D1 Ar file : line : column : \ level : +.Sm on +.Pp +where the fields have the following meanings: +.Bl -tag -width "column" +.It Ar file +The name of the input file causing the message. +.It Ar line +The line number in that input file. +Line numbering starts at 1. +.It Ar column +The column number in that input file. +Column numbering starts at 1. +If the issue is caused by a word, the column number usually +points to the first character of the word. +.It Ar level +The message level, printed in capital letters. +.El +.Pp +Message levels have the following meanings: +.Bl -tag -width "warning" +.It Cm fatal +The parser is unable to parse a given input file at all. +No formatted output is produced from that input file. +.It Cm error +An input file contains syntax that cannot be safely interpreted, +either because it is invalid or because +.Nm +does not implement it yet. +By discarding part of the input or inserting missing tokens, +the parser is able to continue, and the error does not prevent +generation of formatted output, but typically, preparing that +output involves information loss, broken document structure +or unintended formatting. +.It Cm warning +An input file uses obsolete, discouraged or non-portable syntax. +All the same, the meaning of the input is unambiguous and a correct +rendering can be produced. +Documents causing warnings may render poorly when using other +formatting tools instead of +.Nm . +.El +.Pp +Messages of the +.Cm warning +and +.Cm error +levels are hidden unless their level, or a lower level, is requested using a +.Fl W +option or +.Fl T Ns Cm lint +output mode. +.Pp +The +.Nm +utility may also print messages related to invalid command line arguments +or operating system errors, for example when memory is exhausted or +input files cannot be read. +Such messages do not carry the prefix described above. +.Sh COMPATIBILITY +This section summarises +.Nm +compatibility with GNU troff. +Each input and output format is separately noted. +.Ss ASCII Compatibility +.Bl -bullet -compact +.It +Unrenderable unicode codepoints specified with +.Sq \e[uNNNN] +escapes are printed as +.Sq \&? +in mandoc. +In GNU troff, these raise an error. +.It +The +.Sq \&Bd \-literal +and +.Sq \&Bd \-unfilled +macros of +.Xr mdoc 7 +in +.Fl T Ns Cm ascii +are synonyms, as are \-filled and \-ragged. +.It +In historic GNU troff, the +.Sq \&Pa +.Xr mdoc 7 +macro does not underline when scoped under an +.Sq \&It +in the FILES section. +This behaves correctly in +.Nm . +.It +A list or display following the +.Sq \&Ss +.Xr mdoc 7 +macro in +.Fl T Ns Cm ascii +does not assert a prior vertical break, just as it doesn't with +.Sq \&Sh . +.It +The +.Sq \&na +.Xr man 7 +macro in +.Fl T Ns Cm ascii +has no effect. +.It +Words aren't hyphenated. +.El +.Ss HTML/XHTML Compatibility +.Bl -bullet -compact +.It +The +.Sq \efP +escape will revert the font to the previous +.Sq \ef +escape, not to the last rendered decoration, which is now dictated by +CSS instead of hard-coded. +It also will not span past the current scope, +for the same reason. +Note that in +.Sx ASCII Output +mode, this will work fine. +.It +The +.Xr mdoc 7 +.Sq \&Bl \-hang +and +.Sq \&Bl \-tag +list types render similarly (no break following overreached left-hand +side) due to the expressive constraints of HTML. +.It +The +.Xr man 7 +.Sq IP +and +.Sq TP +lists render similarly. +.El +.Sh SEE ALSO +.Xr eqn 7 , +.Xr man 7 , +.Xr mandoc_char 7 , +.Xr mdoc 7 , +.Xr roff 7 , +.Xr tbl 7 +.Sh AUTHORS +The +.Nm +utility was written by +.An Kristaps Dzonsons , +.Mt kristaps@bsd.lv . +.Sh CAVEATS +In +.Fl T Ns Cm html +and +.Fl T Ns Cm xhtml , +the maximum size of an element attribute is determined by +.Dv BUFSIZ , +which is usually 1024 bytes. +Be aware of this when setting long link +formats such as +.Fl O Ns Cm style Ns = Ns Ar really/long/link . +.Pp +Nesting elements within next-line element scopes of +.Fl m Ns Cm an , +such as +.Sq br +within an empty +.Sq B , +will confuse +.Fl T Ns Cm html +and +.Fl T Ns Cm xhtml +and cause them to forget the formatting of the prior next-line scope. +.Pp +The +.Sq \(aq +control character is an alias for the standard macro control character +and does not emit a line-break as stipulated in GNU troff. diff --git a/mandoc.3 b/mandoc.3 new file mode 100644 index 000000000000..4d0b20d6507e --- /dev/null +++ b/mandoc.3 @@ -0,0 +1,600 @@ +.\" $Id: mandoc.3,v 1.17 2012/01/13 15:27:14 joerg Exp $ +.\" +.\" Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> +.\" Copyright (c) 2010 Ingo Schwarze <schwarze@openbsd.org> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: January 13 2012 $ +.Dt MANDOC 3 +.Os +.Sh NAME +.Nm mandoc , +.Nm mandoc_escape , +.Nm man_meta , +.Nm man_mparse , +.Nm man_node , +.Nm mchars_alloc , +.Nm mchars_free , +.Nm mchars_num2char , +.Nm mchars_num2uc , +.Nm mchars_spec2cp , +.Nm mchars_spec2str , +.Nm mdoc_meta , +.Nm mdoc_node , +.Nm mparse_alloc , +.Nm mparse_free , +.Nm mparse_getkeep , +.Nm mparse_keep , +.Nm mparse_readfd , +.Nm mparse_reset , +.Nm mparse_result , +.Nm mparse_strerror , +.Nm mparse_strlevel +.Nd mandoc macro compiler library +.Sh LIBRARY +.Lb mandoc +.Sh SYNOPSIS +.In man.h +.In mdoc.h +.In mandoc.h +.Ft "enum mandoc_esc" +.Fo mandoc_escape +.Fa "const char **end" +.Fa "const char **start" +.Fa "int *sz" +.Fc +.Ft "const struct man_meta *" +.Fo man_meta +.Fa "const struct man *man" +.Fc +.Ft "const struct mparse *" +.Fo man_mparse +.Fa "const struct man *man" +.Fc +.Ft "const struct man_node *" +.Fo man_node +.Fa "const struct man *man" +.Fc +.Ft "struct mchars *" +.Fn mchars_alloc +.Ft void +.Fn mchars_free "struct mchars *p" +.Ft char +.Fn mchars_num2char "const char *cp" "size_t sz" +.Ft int +.Fn mchars_num2uc "const char *cp" "size_t sz" +.Ft "const char *" +.Fo mchars_spec2str +.Fa "const struct mchars *p" +.Fa "const char *cp" +.Fa "size_t sz" +.Fa "size_t *rsz" +.Fc +.Ft int +.Fo mchars_spec2cp +.Fa "const struct mchars *p" +.Fa "const char *cp" +.Fa "size_t sz" +.Ft "const char *" +.Fc +.Ft "const struct mdoc_meta *" +.Fo mdoc_meta +.Fa "const struct mdoc *mdoc" +.Fc +.Ft "const struct mdoc_node *" +.Fo mdoc_node +.Fa "const struct mdoc *mdoc" +.Fc +.Ft void +.Fo mparse_alloc +.Fa "enum mparset type" +.Fa "enum mandoclevel wlevel" +.Fa "mandocmsg msg" +.Fa "void *msgarg" +.Fc +.Ft void +.Fo mparse_free +.Fa "struct mparse *parse" +.Fc +.Ft void +.Fo mparse_getkeep +.Fa "const struct mparse *parse" +.Fc +.Ft void +.Fo mparse_keep +.Fa "struct mparse *parse" +.Fc +.Ft "enum mandoclevel" +.Fo mparse_readfd +.Fa "struct mparse *parse" +.Fa "int fd" +.Fa "const char *fname" +.Fc +.Ft void +.Fo mparse_reset +.Fa "struct mparse *parse" +.Fc +.Ft void +.Fo mparse_result +.Fa "struct mparse *parse" +.Fa "struct mdoc **mdoc" +.Fa "struct man **man" +.Fc +.Ft "const char *" +.Fo mparse_strerror +.Fa "enum mandocerr" +.Fc +.Ft "const char *" +.Fo mparse_strlevel +.Fa "enum mandoclevel" +.Fc +.Vt extern const char * const * man_macronames; +.Vt extern const char * const * mdoc_argnames; +.Vt extern const char * const * mdoc_macronames; +.Fd "#define ASCII_NBRSP" +.Fd "#define ASCII_HYPH" +.Sh DESCRIPTION +The +.Nm mandoc +library parses a +.Ux +manual into an abstract syntax tree (AST). +.Ux +manuals are composed of +.Xr mdoc 7 +or +.Xr man 7 , +and may be mixed with +.Xr roff 7 , +.Xr tbl 7 , +and +.Xr eqn 7 +invocations. +.Pp +The following describes a general parse sequence: +.Bl -enum +.It +initiate a parsing sequence with +.Fn mparse_alloc ; +.It +parse files or file descriptors with +.Fn mparse_readfd ; +.It +retrieve a parsed syntax tree, if the parse was successful, with +.Fn mparse_result ; +.It +iterate over parse nodes with +.Fn mdoc_node +or +.Fn man_node ; +.It +free all allocated memory with +.Fn mparse_free , +or invoke +.Fn mparse_reset +and parse new files. +.El +.Pp +The +.Nm +library also contains routines for translating character strings into glyphs +.Pq see Fn mchars_alloc +and parsing escape sequences from strings +.Pq see Fn mandoc_escape . +.Sh REFERENCE +This section documents the functions, types, and variables available +via +.In mandoc.h . +.Ss Types +.Bl -ohang +.It Vt "enum mandoc_esc" +An escape sequence classification. +.It Vt "enum mandocerr" +A fatal error, error, or warning message during parsing. +.It Vt "enum mandoclevel" +A classification of an +.Vt "enum mandoclevel" +as regards system operation. +.It Vt "struct mchars" +An opaque pointer to an object allowing for translation between +character strings and glyphs. +See +.Fn mchars_alloc . +.It Vt "enum mparset" +The type of parser when reading input. +This should usually be +.Dv MPARSE_AUTO +for auto-detection. +.It Vt "struct mparse" +An opaque pointer to a running parse sequence. +Created with +.Fn mparse_alloc +and freed with +.Fn mparse_free . +This may be used across parsed input if +.Fn mparse_reset +is called between parses. +.It Vt "mandocmsg" +A prototype for a function to handle fatal error, error, and warning +messages emitted by the parser. +.El +.Ss Functions +.Bl -ohang +.It Fn mandoc_escape +Scan an escape sequence, i.e., a character string beginning with +.Sq \e . +Pass a pointer to the character after the +.Sq \e +as +.Va end ; +it will be set to the supremum of the parsed escape sequence unless +returning +.Dv ESCAPE_ERROR , +in which case the string is bogus and should be +thrown away. +If not +.Dv ESCAPE_ERROR +or +.Dv ESCAPE_IGNORE , +.Va start +is set to the first relevant character of the substring (font, glyph, +whatever) of length +.Va sz . +Both +.Va start +and +.Va sz +may be +.Dv NULL . +.It Fn man_meta +Obtain the meta-data of a successful parse. +This may only be used on a pointer returned by +.Fn mparse_result . +.It Fn man_mparse +Get the parser used for the current output. +.It Fn man_node +Obtain the root node of a successful parse. +This may only be used on a pointer returned by +.Fn mparse_result . +.It Fn mchars_alloc +Allocate an +.Vt "struct mchars *" +object for translating special characters into glyphs. +See +.Xr mandoc_char 7 +for an overview of special characters. +The object must be freed with +.Fn mchars_free . +.It Fn mchars_free +Free an object created with +.Fn mchars_alloc . +.It Fn mchars_num2char +Convert a character index (e.g., the \eN\(aq\(aq escape) into a +printable ASCII character. +Returns \e0 (the nil character) if the input sequence is malformed. +.It Fn mchars_num2uc +Convert a hexadecimal character index (e.g., the \e[uNNNN] escape) into +a Unicode codepoint. +Returns \e0 (the nil character) if the input sequence is malformed. +.It Fn mchars_spec2cp +Convert a special character into a valid Unicode codepoint. +Returns \-1 on failure or a non-zero Unicode codepoint on success. +.It Fn mchars_spec2str +Convert a special character into an ASCII string. +Returns +.Dv NULL +on failure. +.It Fn mdoc_meta +Obtain the meta-data of a successful parse. +This may only be used on a pointer returned by +.Fn mparse_result . +.It Fn mdoc_node +Obtain the root node of a successful parse. +This may only be used on a pointer returned by +.Fn mparse_result . +.It Fn mparse_alloc +Allocate a parser. +The same parser may be used for multiple files so long as +.Fn mparse_reset +is called between parses. +.Fn mparse_free +must be called to free the memory allocated by this function. +.It Fn mparse_free +Free all memory allocated by +.Fn mparse_alloc . +.It Fn mparse_getkeep +Acquire the keep buffer. +Must follow a call of +.Fn mparse_keep . +.It Fn mparse_keep +Instruct the parser to retain a copy of its parsed input. +This can be acquired with subsequent +.Fn mparse_getkeep +calls. +.It Fn mparse_readfd +Parse a file or file descriptor. +If +.Va fd +is -1, +.Va fname +is opened for reading. +Otherwise, +.Va fname +is assumed to be the name associated with +.Va fd . +This may be called multiple times with different parameters; however, +.Fn mparse_reset +should be invoked between parses. +.It Fn mparse_reset +Reset a parser so that +.Fn mparse_readfd +may be used again. +.It Fn mparse_result +Obtain the result of a parse. +Only successful parses +.Po +i.e., those where +.Fn mparse_readfd +returned less than MANDOCLEVEL_FATAL +.Pc +should invoke this function, in which case one of the two pointers will +be filled in. +.It Fn mparse_strerror +Return a statically-allocated string representation of an error code. +.It Fn mparse_strlevel +Return a statically-allocated string representation of a level code. +.El +.Ss Variables +.Bl -ohang +.It Va man_macronames +The string representation of a man macro as indexed by +.Vt "enum mant" . +.It Va mdoc_argnames +The string representation of a mdoc macro argument as indexed by +.Vt "enum mdocargt" . +.It Va mdoc_macronames +The string representation of a mdoc macro as indexed by +.Vt "enum mdoct" . +.El +.Sh IMPLEMENTATION NOTES +This section consists of structural documentation for +.Xr mdoc 7 +and +.Xr man 7 +syntax trees and strings. +.Ss Man and Mdoc Strings +Strings may be extracted from mdoc and man meta-data, or from text +nodes (MDOC_TEXT and MAN_TEXT, respectively). +These strings have special non-printing formatting cues embedded in the +text itself, as well as +.Xr roff 7 +escapes preserved from input. +Implementing systems will need to handle both situations to produce +human-readable text. +In general, strings may be assumed to consist of 7-bit ASCII characters. +.Pp +The following non-printing characters may be embedded in text strings: +.Bl -tag -width Ds +.It Dv ASCII_NBRSP +A non-breaking space character. +.It Dv ASCII_HYPH +A soft hyphen. +.El +.Pp +Escape characters are also passed verbatim into text strings. +An escape character is a sequence of characters beginning with the +backslash +.Pq Sq \e . +To construct human-readable text, these should be intercepted with +.Fn mandoc_escape +and converted with one of +.Fn mchars_num2char , +.Fn mchars_spec2str , +and so on. +.Ss Man Abstract Syntax Tree +This AST is governed by the ontological rules dictated in +.Xr man 7 +and derives its terminology accordingly. +.Pp +The AST is composed of +.Vt struct man_node +nodes with element, root and text types as declared by the +.Va type +field. +Each node also provides its parse point (the +.Va line , +.Va sec , +and +.Va pos +fields), its position in the tree (the +.Va parent , +.Va child , +.Va next +and +.Va prev +fields) and some type-specific data. +.Pp +The tree itself is arranged according to the following normal form, +where capitalised non-terminals represent nodes. +.Pp +.Bl -tag -width "ELEMENTXX" -compact +.It ROOT +\(<- mnode+ +.It mnode +\(<- ELEMENT | TEXT | BLOCK +.It BLOCK +\(<- HEAD BODY +.It HEAD +\(<- mnode* +.It BODY +\(<- mnode* +.It ELEMENT +\(<- ELEMENT | TEXT* +.It TEXT +\(<- [[:ascii:]]* +.El +.Pp +The only elements capable of nesting other elements are those with +next-lint scope as documented in +.Xr man 7 . +.Ss Mdoc Abstract Syntax Tree +This AST is governed by the ontological +rules dictated in +.Xr mdoc 7 +and derives its terminology accordingly. +.Qq In-line +elements described in +.Xr mdoc 7 +are described simply as +.Qq elements . +.Pp +The AST is composed of +.Vt struct mdoc_node +nodes with block, head, body, element, root and text types as declared +by the +.Va type +field. +Each node also provides its parse point (the +.Va line , +.Va sec , +and +.Va pos +fields), its position in the tree (the +.Va parent , +.Va child , +.Va nchild , +.Va next +and +.Va prev +fields) and some type-specific data, in particular, for nodes generated +from macros, the generating macro in the +.Va tok +field. +.Pp +The tree itself is arranged according to the following normal form, +where capitalised non-terminals represent nodes. +.Pp +.Bl -tag -width "ELEMENTXX" -compact +.It ROOT +\(<- mnode+ +.It mnode +\(<- BLOCK | ELEMENT | TEXT +.It BLOCK +\(<- HEAD [TEXT] (BODY [TEXT])+ [TAIL [TEXT]] +.It ELEMENT +\(<- TEXT* +.It HEAD +\(<- mnode* +.It BODY +\(<- mnode* [ENDBODY mnode*] +.It TAIL +\(<- mnode* +.It TEXT +\(<- [[:ascii:]]* +.El +.Pp +Of note are the TEXT nodes following the HEAD, BODY and TAIL nodes of +the BLOCK production: these refer to punctuation marks. +Furthermore, although a TEXT node will generally have a non-zero-length +string, in the specific case of +.Sq \&.Bd \-literal , +an empty line will produce a zero-length string. +Multiple body parts are only found in invocations of +.Sq \&Bl \-column , +where a new body introduces a new phrase. +.Pp +The +.Xr mdoc 7 +syntax tree accommodates for broken block structures as well. +The ENDBODY node is available to end the formatting associated +with a given block before the physical end of that block. +It has a non-null +.Va end +field, is of the BODY +.Va type , +has the same +.Va tok +as the BLOCK it is ending, and has a +.Va pending +field pointing to that BLOCK's BODY node. +It is an indirect child of that BODY node +and has no children of its own. +.Pp +An ENDBODY node is generated when a block ends while one of its child +blocks is still open, like in the following example: +.Bd -literal -offset indent +\&.Ao ao +\&.Bo bo ac +\&.Ac bc +\&.Bc end +.Ed +.Pp +This example results in the following block structure: +.Bd -literal -offset indent +BLOCK Ao + HEAD Ao + BODY Ao + TEXT ao + BLOCK Bo, pending -> Ao + HEAD Bo + BODY Bo + TEXT bo + TEXT ac + ENDBODY Ao, pending -> Ao + TEXT bc +TEXT end +.Ed +.Pp +Here, the formatting of the +.Sq \&Ao +block extends from TEXT ao to TEXT ac, +while the formatting of the +.Sq \&Bo +block extends from TEXT bo to TEXT bc. +It renders as follows in +.Fl T Ns Cm ascii +mode: +.Pp +.Dl <ao [bo ac> bc] end +.Pp +Support for badly-nested blocks is only provided for backward +compatibility with some older +.Xr mdoc 7 +implementations. +Using badly-nested blocks is +.Em strongly discouraged ; +for example, the +.Fl T Ns Cm html +and +.Fl T Ns Cm xhtml +front-ends to +.Xr mandoc 1 +are unable to render them in any meaningful way. +Furthermore, behaviour when encountering badly-nested blocks is not +consistent across troff implementations, especially when using multiple +levels of badly-nested blocks. +.Sh SEE ALSO +.Xr mandoc 1 , +.Xr eqn 7 , +.Xr man 7 , +.Xr mandoc_char 7 , +.Xr mdoc 7 , +.Xr roff 7 , +.Xr tbl 7 +.Sh AUTHORS +The +.Nm +library was written by +.An Kristaps Dzonsons , +.Mt kristaps@bsd.lv . diff --git a/mandoc.c b/mandoc.c new file mode 100644 index 000000000000..604bb67e6ae4 --- /dev/null +++ b/mandoc.c @@ -0,0 +1,735 @@ +/* $Id: mandoc.c,v 1.62 2011/12/03 16:08:51 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/types.h> + +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <time.h> + +#include "mandoc.h" +#include "libmandoc.h" + +#define DATESIZE 32 + +static int a2time(time_t *, const char *, const char *); +static char *time2a(time_t); +static int numescape(const char *); + +/* + * Pass over recursive numerical expressions. This context of this + * function is important: it's only called within character-terminating + * escapes (e.g., \s[xxxyyy]), so all we need to do is handle initial + * recursion: we don't care about what's in these blocks. + * This returns the number of characters skipped or -1 if an error + * occurs (the caller should bail). + */ +static int +numescape(const char *start) +{ + int i; + size_t sz; + const char *cp; + + i = 0; + + /* The expression consists of a subexpression. */ + + if ('\\' == start[i]) { + cp = &start[++i]; + /* + * Read past the end of the subexpression. + * Bail immediately on errors. + */ + if (ESCAPE_ERROR == mandoc_escape(&cp, NULL, NULL)) + return(-1); + return(i + cp - &start[i]); + } + + if ('(' != start[i++]) + return(0); + + /* + * A parenthesised subexpression. Read until the closing + * parenthesis, making sure to handle any nested subexpressions + * that might ruin our parse. + */ + + while (')' != start[i]) { + sz = strcspn(&start[i], ")\\"); + i += (int)sz; + + if ('\0' == start[i]) + return(-1); + else if ('\\' != start[i]) + continue; + + cp = &start[++i]; + if (ESCAPE_ERROR == mandoc_escape(&cp, NULL, NULL)) + return(-1); + i += cp - &start[i]; + } + + /* Read past the terminating ')'. */ + return(++i); +} + +enum mandoc_esc +mandoc_escape(const char **end, const char **start, int *sz) +{ + char c, term, numeric; + int i, lim, ssz, rlim; + const char *cp, *rstart; + enum mandoc_esc gly; + + cp = *end; + rstart = cp; + if (start) + *start = rstart; + i = lim = 0; + gly = ESCAPE_ERROR; + term = numeric = '\0'; + + switch ((c = cp[i++])) { + /* + * First the glyphs. There are several different forms of + * these, but each eventually returns a substring of the glyph + * name. + */ + case ('('): + gly = ESCAPE_SPECIAL; + lim = 2; + break; + case ('['): + gly = ESCAPE_SPECIAL; + /* + * Unicode escapes are defined in groff as \[uXXXX] to + * \[u10FFFF], where the contained value must be a valid + * Unicode codepoint. Here, however, only check whether + * it's not a zero-width escape. + */ + if ('u' == cp[i] && ']' != cp[i + 1]) + gly = ESCAPE_UNICODE; + term = ']'; + break; + case ('C'): + if ('\'' != cp[i]) + return(ESCAPE_ERROR); + gly = ESCAPE_SPECIAL; + term = '\''; + break; + + /* + * Handle all triggers matching \X(xy, \Xx, and \X[xxxx], where + * 'X' is the trigger. These have opaque sub-strings. + */ + case ('F'): + /* FALLTHROUGH */ + case ('g'): + /* FALLTHROUGH */ + case ('k'): + /* FALLTHROUGH */ + case ('M'): + /* FALLTHROUGH */ + case ('m'): + /* FALLTHROUGH */ + case ('n'): + /* FALLTHROUGH */ + case ('V'): + /* FALLTHROUGH */ + case ('Y'): + gly = ESCAPE_IGNORE; + /* FALLTHROUGH */ + case ('f'): + if (ESCAPE_ERROR == gly) + gly = ESCAPE_FONT; + + rstart= &cp[i]; + if (start) + *start = rstart; + + switch (cp[i++]) { + case ('('): + lim = 2; + break; + case ('['): + term = ']'; + break; + default: + lim = 1; + i--; + break; + } + break; + + /* + * These escapes are of the form \X'Y', where 'X' is the trigger + * and 'Y' is any string. These have opaque sub-strings. + */ + case ('A'): + /* FALLTHROUGH */ + case ('b'): + /* FALLTHROUGH */ + case ('D'): + /* FALLTHROUGH */ + case ('o'): + /* FALLTHROUGH */ + case ('R'): + /* FALLTHROUGH */ + case ('X'): + /* FALLTHROUGH */ + case ('Z'): + if ('\'' != cp[i++]) + return(ESCAPE_ERROR); + gly = ESCAPE_IGNORE; + term = '\''; + break; + + /* + * These escapes are of the form \X'N', where 'X' is the trigger + * and 'N' resolves to a numerical expression. + */ + case ('B'): + /* FALLTHROUGH */ + case ('h'): + /* FALLTHROUGH */ + case ('H'): + /* FALLTHROUGH */ + case ('L'): + /* FALLTHROUGH */ + case ('l'): + gly = ESCAPE_NUMBERED; + /* FALLTHROUGH */ + case ('S'): + /* FALLTHROUGH */ + case ('v'): + /* FALLTHROUGH */ + case ('w'): + /* FALLTHROUGH */ + case ('x'): + if (ESCAPE_ERROR == gly) + gly = ESCAPE_IGNORE; + if ('\'' != cp[i++]) + return(ESCAPE_ERROR); + term = numeric = '\''; + break; + + /* + * Special handling for the numbered character escape. + * XXX Do any other escapes need similar handling? + */ + case ('N'): + if ('\0' == cp[i]) + return(ESCAPE_ERROR); + *end = &cp[++i]; + if (isdigit((unsigned char)cp[i-1])) + return(ESCAPE_IGNORE); + while (isdigit((unsigned char)**end)) + (*end)++; + if (start) + *start = &cp[i]; + if (sz) + *sz = *end - &cp[i]; + if ('\0' != **end) + (*end)++; + return(ESCAPE_NUMBERED); + + /* + * Sizes get a special category of their own. + */ + case ('s'): + gly = ESCAPE_IGNORE; + + rstart = &cp[i]; + if (start) + *start = rstart; + + /* See +/- counts as a sign. */ + c = cp[i]; + if ('+' == c || '-' == c || ASCII_HYPH == c) + ++i; + + switch (cp[i++]) { + case ('('): + lim = 2; + break; + case ('['): + term = numeric = ']'; + break; + case ('\''): + term = numeric = '\''; + break; + default: + lim = 1; + i--; + break; + } + + /* See +/- counts as a sign. */ + c = cp[i]; + if ('+' == c || '-' == c || ASCII_HYPH == c) + ++i; + + break; + + /* + * Anything else is assumed to be a glyph. + */ + default: + gly = ESCAPE_SPECIAL; + lim = 1; + i--; + break; + } + + assert(ESCAPE_ERROR != gly); + + rstart = &cp[i]; + if (start) + *start = rstart; + + /* + * If a terminating block has been specified, we need to + * handle the case of recursion, which could have their + * own terminating blocks that mess up our parse. This, by the + * way, means that the "start" and "size" values will be + * effectively meaningless. + */ + + ssz = 0; + if (numeric && -1 == (ssz = numescape(&cp[i]))) + return(ESCAPE_ERROR); + + i += ssz; + rlim = -1; + + /* + * We have a character terminator. Try to read up to that + * character. If we can't (i.e., we hit the nil), then return + * an error; if we can, calculate our length, read past the + * terminating character, and exit. + */ + + if ('\0' != term) { + *end = strchr(&cp[i], term); + if ('\0' == *end) + return(ESCAPE_ERROR); + + rlim = *end - &cp[i]; + if (sz) + *sz = rlim; + (*end)++; + goto out; + } + + assert(lim > 0); + + /* + * We have a numeric limit. If the string is shorter than that, + * stop and return an error. Else adjust our endpoint, length, + * and return the current glyph. + */ + + if ((size_t)lim > strlen(&cp[i])) + return(ESCAPE_ERROR); + + rlim = lim; + if (sz) + *sz = rlim; + + *end = &cp[i] + lim; + +out: + assert(rlim >= 0 && rstart); + + /* Run post-processors. */ + + switch (gly) { + case (ESCAPE_FONT): + /* + * Pretend that the constant-width font modes are the + * same as the regular font modes. + */ + if (2 == rlim && 'C' == *rstart) + rstart++; + else if (1 != rlim) + break; + + switch (*rstart) { + case ('3'): + /* FALLTHROUGH */ + case ('B'): + gly = ESCAPE_FONTBOLD; + break; + case ('2'): + /* FALLTHROUGH */ + case ('I'): + gly = ESCAPE_FONTITALIC; + break; + case ('P'): + gly = ESCAPE_FONTPREV; + break; + case ('1'): + /* FALLTHROUGH */ + case ('R'): + gly = ESCAPE_FONTROMAN; + break; + } + break; + case (ESCAPE_SPECIAL): + if (1 != rlim) + break; + if ('c' == *rstart) + gly = ESCAPE_NOSPACE; + break; + default: + break; + } + + return(gly); +} + +void * +mandoc_calloc(size_t num, size_t size) +{ + void *ptr; + + ptr = calloc(num, size); + if (NULL == ptr) { + perror(NULL); + exit((int)MANDOCLEVEL_SYSERR); + } + + return(ptr); +} + + +void * +mandoc_malloc(size_t size) +{ + void *ptr; + + ptr = malloc(size); + if (NULL == ptr) { + perror(NULL); + exit((int)MANDOCLEVEL_SYSERR); + } + + return(ptr); +} + + +void * +mandoc_realloc(void *ptr, size_t size) +{ + + ptr = realloc(ptr, size); + if (NULL == ptr) { + perror(NULL); + exit((int)MANDOCLEVEL_SYSERR); + } + + return(ptr); +} + +char * +mandoc_strndup(const char *ptr, size_t sz) +{ + char *p; + + p = mandoc_malloc(sz + 1); + memcpy(p, ptr, sz); + p[(int)sz] = '\0'; + return(p); +} + +char * +mandoc_strdup(const char *ptr) +{ + char *p; + + p = strdup(ptr); + if (NULL == p) { + perror(NULL); + exit((int)MANDOCLEVEL_SYSERR); + } + + return(p); +} + +/* + * Parse a quoted or unquoted roff-style request or macro argument. + * Return a pointer to the parsed argument, which is either the original + * pointer or advanced by one byte in case the argument is quoted. + * Null-terminate the argument in place. + * Collapse pairs of quotes inside quoted arguments. + * Advance the argument pointer to the next argument, + * or to the null byte terminating the argument line. + */ +char * +mandoc_getarg(struct mparse *parse, char **cpp, int ln, int *pos) +{ + char *start, *cp; + int quoted, pairs, white; + + /* Quoting can only start with a new word. */ + start = *cpp; + quoted = 0; + if ('"' == *start) { + quoted = 1; + start++; + } + + pairs = 0; + white = 0; + for (cp = start; '\0' != *cp; cp++) { + /* Move left after quoted quotes and escaped backslashes. */ + if (pairs) + cp[-pairs] = cp[0]; + if ('\\' == cp[0]) { + if ('\\' == cp[1]) { + /* Poor man's copy mode. */ + pairs++; + cp++; + } else if (0 == quoted && ' ' == cp[1]) + /* Skip escaped blanks. */ + cp++; + } else if (0 == quoted) { + if (' ' == cp[0]) { + /* Unescaped blanks end unquoted args. */ + white = 1; + break; + } + } else if ('"' == cp[0]) { + if ('"' == cp[1]) { + /* Quoted quotes collapse. */ + pairs++; + cp++; + } else { + /* Unquoted quotes end quoted args. */ + quoted = 2; + break; + } + } + } + + /* Quoted argument without a closing quote. */ + if (1 == quoted) + mandoc_msg(MANDOCERR_BADQUOTE, parse, ln, *pos, NULL); + + /* Null-terminate this argument and move to the next one. */ + if (pairs) + cp[-pairs] = '\0'; + if ('\0' != *cp) { + *cp++ = '\0'; + while (' ' == *cp) + cp++; + } + *pos += (int)(cp - start) + (quoted ? 1 : 0); + *cpp = cp; + + if ('\0' == *cp && (white || ' ' == cp[-1])) + mandoc_msg(MANDOCERR_EOLNSPACE, parse, ln, *pos, NULL); + + return(start); +} + +static int +a2time(time_t *t, const char *fmt, const char *p) +{ + struct tm tm; + char *pp; + + memset(&tm, 0, sizeof(struct tm)); + + pp = NULL; +#ifdef HAVE_STRPTIME + pp = strptime(p, fmt, &tm); +#endif + if (NULL != pp && '\0' == *pp) { + *t = mktime(&tm); + return(1); + } + + return(0); +} + +static char * +time2a(time_t t) +{ + struct tm *tm; + char *buf, *p; + size_t ssz; + int isz; + + tm = localtime(&t); + + /* + * Reserve space: + * up to 9 characters for the month (September) + blank + * up to 2 characters for the day + comma + blank + * 4 characters for the year and a terminating '\0' + */ + p = buf = mandoc_malloc(10 + 4 + 4 + 1); + + if (0 == (ssz = strftime(p, 10 + 1, "%B ", tm))) + goto fail; + p += (int)ssz; + + if (-1 == (isz = snprintf(p, 4 + 1, "%d, ", tm->tm_mday))) + goto fail; + p += isz; + + if (0 == strftime(p, 4 + 1, "%Y", tm)) + goto fail; + return(buf); + +fail: + free(buf); + return(NULL); +} + +char * +mandoc_normdate(struct mparse *parse, char *in, int ln, int pos) +{ + char *out; + time_t t; + + if (NULL == in || '\0' == *in || + 0 == strcmp(in, "$" "Mdocdate$")) { + mandoc_msg(MANDOCERR_NODATE, parse, ln, pos, NULL); + time(&t); + } + else if (a2time(&t, "%Y-%m-%d", in)) + t = 0; + else if (!a2time(&t, "$" "Mdocdate: %b %d %Y $", in) && + !a2time(&t, "%b %d, %Y", in)) { + mandoc_msg(MANDOCERR_BADDATE, parse, ln, pos, NULL); + t = 0; + } + out = t ? time2a(t) : NULL; + return(out ? out : mandoc_strdup(in)); +} + +int +mandoc_eos(const char *p, size_t sz, int enclosed) +{ + const char *q; + int found; + + if (0 == sz) + return(0); + + /* + * End-of-sentence recognition must include situations where + * some symbols, such as `)', allow prior EOS punctuation to + * propagate outward. + */ + + found = 0; + for (q = p + (int)sz - 1; q >= p; q--) { + switch (*q) { + case ('\"'): + /* FALLTHROUGH */ + case ('\''): + /* FALLTHROUGH */ + case (']'): + /* FALLTHROUGH */ + case (')'): + if (0 == found) + enclosed = 1; + break; + case ('.'): + /* FALLTHROUGH */ + case ('!'): + /* FALLTHROUGH */ + case ('?'): + found = 1; + break; + default: + return(found && (!enclosed || isalnum((unsigned char)*q))); + } + } + + return(found && !enclosed); +} + +/* + * Find out whether a line is a macro line or not. If it is, adjust the + * current position and return one; if it isn't, return zero and don't + * change the current position. + */ +int +mandoc_getcontrol(const char *cp, int *ppos) +{ + int pos; + + pos = *ppos; + + if ('\\' == cp[pos] && '.' == cp[pos + 1]) + pos += 2; + else if ('.' == cp[pos] || '\'' == cp[pos]) + pos++; + else + return(0); + + while (' ' == cp[pos] || '\t' == cp[pos]) + pos++; + + *ppos = pos; + return(1); +} + +/* + * Convert a string to a long that may not be <0. + * If the string is invalid, or is less than 0, return -1. + */ +int +mandoc_strntoi(const char *p, size_t sz, int base) +{ + char buf[32]; + char *ep; + long v; + + if (sz > 31) + return(-1); + + memcpy(buf, p, sz); + buf[(int)sz] = '\0'; + + errno = 0; + v = strtol(buf, &ep, base); + + if (buf[0] == '\0' || *ep != '\0') + return(-1); + + if (v > INT_MAX) + v = INT_MAX; + if (v < INT_MIN) + v = INT_MIN; + + return((int)v); +} diff --git a/mandoc.h b/mandoc.h new file mode 100644 index 000000000000..a37effc5f58e --- /dev/null +++ b/mandoc.h @@ -0,0 +1,432 @@ +/* $Id: mandoc.h,v 1.99 2012/02/16 20:51:31 joerg Exp $ */ +/* + * Copyright (c) 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef MANDOC_H +#define MANDOC_H + +#define ASCII_NBRSP 31 /* non-breaking space */ +#define ASCII_HYPH 30 /* breakable hyphen */ + +/* + * Status level. This refers to both internal status (i.e., whilst + * running, when warnings/errors are reported) and an indicator of a + * threshold of when to halt (when said internal state exceeds the + * threshold). + */ +enum mandoclevel { + MANDOCLEVEL_OK = 0, + MANDOCLEVEL_RESERVED, + MANDOCLEVEL_WARNING, /* warnings: syntax, whitespace, etc. */ + MANDOCLEVEL_ERROR, /* input has been thrown away */ + MANDOCLEVEL_FATAL, /* input is borked */ + MANDOCLEVEL_BADARG, /* bad argument in invocation */ + MANDOCLEVEL_SYSERR, /* system error */ + MANDOCLEVEL_MAX +}; + +/* + * All possible things that can go wrong within a parse, be it libroff, + * libmdoc, or libman. + */ +enum mandocerr { + MANDOCERR_OK, + + MANDOCERR_WARNING, /* ===== start of warnings ===== */ + + /* related to the prologue */ + MANDOCERR_NOTITLE, /* no title in document */ + MANDOCERR_UPPERCASE, /* document title should be all caps */ + MANDOCERR_BADMSEC, /* unknown manual section */ + MANDOCERR_NODATE, /* date missing, using today's date */ + MANDOCERR_BADDATE, /* cannot parse date, using it verbatim */ + MANDOCERR_PROLOGOOO, /* prologue macros out of order */ + MANDOCERR_PROLOGREP, /* duplicate prologue macro */ + MANDOCERR_BADPROLOG, /* macro not allowed in prologue */ + MANDOCERR_BADBODY, /* macro not allowed in body */ + + /* related to document structure */ + MANDOCERR_SO, /* .so is fragile, better use ln(1) */ + MANDOCERR_NAMESECFIRST, /* NAME section must come first */ + MANDOCERR_BADNAMESEC, /* bad NAME section contents */ + MANDOCERR_NONAME, /* manual name not yet set */ + MANDOCERR_SECOOO, /* sections out of conventional order */ + MANDOCERR_SECREP, /* duplicate section name */ + MANDOCERR_SECMSEC, /* section not in conventional manual section */ + + /* related to macros and nesting */ + MANDOCERR_MACROOBS, /* skipping obsolete macro */ + MANDOCERR_IGNPAR, /* skipping paragraph macro */ + MANDOCERR_IGNNS, /* skipping no-space macro */ + MANDOCERR_SCOPENEST, /* blocks badly nested */ + MANDOCERR_CHILD, /* child violates parent syntax */ + MANDOCERR_NESTEDDISP, /* nested displays are not portable */ + MANDOCERR_SCOPEREP, /* already in literal mode */ + MANDOCERR_LINESCOPE, /* line scope broken */ + + /* related to missing macro arguments */ + MANDOCERR_MACROEMPTY, /* skipping empty macro */ + MANDOCERR_ARGCWARN, /* argument count wrong */ + MANDOCERR_DISPTYPE, /* missing display type */ + MANDOCERR_LISTFIRST, /* list type must come first */ + MANDOCERR_NOWIDTHARG, /* tag lists require a width argument */ + MANDOCERR_FONTTYPE, /* missing font type */ + MANDOCERR_WNOSCOPE, /* skipping end of block that is not open */ + + /* related to bad macro arguments */ + MANDOCERR_IGNARGV, /* skipping argument */ + MANDOCERR_ARGVREP, /* duplicate argument */ + MANDOCERR_DISPREP, /* duplicate display type */ + MANDOCERR_LISTREP, /* duplicate list type */ + MANDOCERR_BADATT, /* unknown AT&T UNIX version */ + MANDOCERR_BADBOOL, /* bad Boolean value */ + MANDOCERR_BADFONT, /* unknown font */ + MANDOCERR_BADSTANDARD, /* unknown standard specifier */ + MANDOCERR_BADWIDTH, /* bad width argument */ + + /* related to plain text */ + MANDOCERR_NOBLANKLN, /* blank line in non-literal context */ + MANDOCERR_BADTAB, /* tab in non-literal context */ + MANDOCERR_EOLNSPACE, /* end of line whitespace */ + MANDOCERR_BADCOMMENT, /* bad comment style */ + MANDOCERR_BADESCAPE, /* unknown escape sequence */ + MANDOCERR_BADQUOTE, /* unterminated quoted string */ + + /* related to equations */ + MANDOCERR_EQNQUOTE, /* unexpected literal in equation */ + + MANDOCERR_ERROR, /* ===== start of errors ===== */ + + /* related to equations */ + MANDOCERR_EQNNSCOPE, /* unexpected equation scope closure*/ + MANDOCERR_EQNSCOPE, /* equation scope open on exit */ + MANDOCERR_EQNBADSCOPE, /* overlapping equation scopes */ + MANDOCERR_EQNEOF, /* unexpected end of equation */ + MANDOCERR_EQNSYNT, /* equation syntax error */ + + /* related to tables */ + MANDOCERR_TBL, /* bad table syntax */ + MANDOCERR_TBLOPT, /* bad table option */ + MANDOCERR_TBLLAYOUT, /* bad table layout */ + MANDOCERR_TBLNOLAYOUT, /* no table layout cells specified */ + MANDOCERR_TBLNODATA, /* no table data cells specified */ + MANDOCERR_TBLIGNDATA, /* ignore data in cell */ + MANDOCERR_TBLBLOCK, /* data block still open */ + MANDOCERR_TBLEXTRADAT, /* ignoring extra data cells */ + + MANDOCERR_ROFFLOOP, /* input stack limit exceeded, infinite loop? */ + MANDOCERR_BADCHAR, /* skipping bad character */ + MANDOCERR_NAMESC, /* escaped character not allowed in a name */ + MANDOCERR_NOTEXT, /* skipping text before the first section header */ + MANDOCERR_MACRO, /* skipping unknown macro */ + MANDOCERR_REQUEST, /* NOT IMPLEMENTED: skipping request */ + MANDOCERR_ARGCOUNT, /* argument count wrong */ + MANDOCERR_NOSCOPE, /* skipping end of block that is not open */ + MANDOCERR_SCOPEBROKEN, /* missing end of block */ + MANDOCERR_SCOPEEXIT, /* scope open on exit */ + MANDOCERR_UNAME, /* uname(3) system call failed */ + /* FIXME: merge following with MANDOCERR_ARGCOUNT */ + MANDOCERR_NOARGS, /* macro requires line argument(s) */ + MANDOCERR_NOBODY, /* macro requires body argument(s) */ + MANDOCERR_NOARGV, /* macro requires argument(s) */ + MANDOCERR_LISTTYPE, /* missing list type */ + MANDOCERR_ARGSLOST, /* line argument(s) will be lost */ + MANDOCERR_BODYLOST, /* body argument(s) will be lost */ + + MANDOCERR_FATAL, /* ===== start of fatal errors ===== */ + + MANDOCERR_NOTMANUAL, /* manual isn't really a manual */ + MANDOCERR_COLUMNS, /* column syntax is inconsistent */ + MANDOCERR_BADDISP, /* NOT IMPLEMENTED: .Bd -file */ + MANDOCERR_SYNTARGVCOUNT, /* argument count wrong, violates syntax */ + MANDOCERR_SYNTCHILD, /* child violates parent syntax */ + MANDOCERR_SYNTARGCOUNT, /* argument count wrong, violates syntax */ + MANDOCERR_SOPATH, /* NOT IMPLEMENTED: .so with absolute path or ".." */ + MANDOCERR_NODOCBODY, /* no document body */ + MANDOCERR_NODOCPROLOG, /* no document prologue */ + MANDOCERR_MEM, /* static buffer exhausted */ + MANDOCERR_MAX +}; + +struct tbl { + char tab; /* cell-separator */ + char decimal; /* decimal point */ + int linesize; + int opts; +#define TBL_OPT_CENTRE (1 << 0) +#define TBL_OPT_EXPAND (1 << 1) +#define TBL_OPT_BOX (1 << 2) +#define TBL_OPT_DBOX (1 << 3) +#define TBL_OPT_ALLBOX (1 << 4) +#define TBL_OPT_NOKEEP (1 << 5) +#define TBL_OPT_NOSPACE (1 << 6) + int cols; /* number of columns */ +}; + +enum tbl_headt { + TBL_HEAD_DATA, /* plug in data from tbl_dat */ + TBL_HEAD_VERT, /* vertical spacer */ + TBL_HEAD_DVERT /* double-vertical spacer */ +}; + +/* + * The head of a table specifies all of its columns. When formatting a + * tbl_span, iterate over these and plug in data from the tbl_span when + * appropriate, using tbl_cell as a guide to placement. + */ +struct tbl_head { + enum tbl_headt pos; + int ident; /* 0 <= unique id < cols */ + struct tbl_head *next; + struct tbl_head *prev; +}; + +enum tbl_cellt { + TBL_CELL_CENTRE, /* c, C */ + TBL_CELL_RIGHT, /* r, R */ + TBL_CELL_LEFT, /* l, L */ + TBL_CELL_NUMBER, /* n, N */ + TBL_CELL_SPAN, /* s, S */ + TBL_CELL_LONG, /* a, A */ + TBL_CELL_DOWN, /* ^ */ + TBL_CELL_HORIZ, /* _, - */ + TBL_CELL_DHORIZ, /* = */ + TBL_CELL_VERT, /* | */ + TBL_CELL_DVERT, /* || */ + TBL_CELL_MAX +}; + +/* + * A cell in a layout row. + */ +struct tbl_cell { + struct tbl_cell *next; + enum tbl_cellt pos; + size_t spacing; + int flags; +#define TBL_CELL_TALIGN (1 << 0) /* t, T */ +#define TBL_CELL_BALIGN (1 << 1) /* d, D */ +#define TBL_CELL_BOLD (1 << 2) /* fB, B, b */ +#define TBL_CELL_ITALIC (1 << 3) /* fI, I, i */ +#define TBL_CELL_EQUAL (1 << 4) /* e, E */ +#define TBL_CELL_UP (1 << 5) /* u, U */ +#define TBL_CELL_WIGN (1 << 6) /* z, Z */ + struct tbl_head *head; +}; + +/* + * A layout row. + */ +struct tbl_row { + struct tbl_row *next; + struct tbl_cell *first; + struct tbl_cell *last; +}; + +enum tbl_datt { + TBL_DATA_NONE, /* has no data */ + TBL_DATA_DATA, /* consists of data/string */ + TBL_DATA_HORIZ, /* horizontal line */ + TBL_DATA_DHORIZ, /* double-horizontal line */ + TBL_DATA_NHORIZ, /* squeezed horizontal line */ + TBL_DATA_NDHORIZ /* squeezed double-horizontal line */ +}; + +/* + * A cell within a row of data. The "string" field contains the actual + * string value that's in the cell. The rest is layout. + */ +struct tbl_dat { + struct tbl_cell *layout; /* layout cell */ + int spans; /* how many spans follow */ + struct tbl_dat *next; + char *string; /* data (NULL if not TBL_DATA_DATA) */ + enum tbl_datt pos; +}; + +enum tbl_spant { + TBL_SPAN_DATA, /* span consists of data */ + TBL_SPAN_HORIZ, /* span is horizontal line */ + TBL_SPAN_DHORIZ /* span is double horizontal line */ +}; + +/* + * A row of data in a table. + */ +struct tbl_span { + struct tbl *tbl; + struct tbl_head *head; + struct tbl_row *layout; /* layout row */ + struct tbl_dat *first; + struct tbl_dat *last; + int line; /* parse line */ + int flags; +#define TBL_SPAN_FIRST (1 << 0) +#define TBL_SPAN_LAST (1 << 1) + enum tbl_spant pos; + struct tbl_span *next; +}; + +enum eqn_boxt { + EQN_ROOT, /* root of parse tree */ + EQN_TEXT, /* text (number, variable, whatever) */ + EQN_SUBEXPR, /* nested `eqn' subexpression */ + EQN_LIST, /* subexpressions list */ + EQN_MATRIX /* matrix subexpression */ +}; + +enum eqn_markt { + EQNMARK_NONE = 0, + EQNMARK_DOT, + EQNMARK_DOTDOT, + EQNMARK_HAT, + EQNMARK_TILDE, + EQNMARK_VEC, + EQNMARK_DYAD, + EQNMARK_BAR, + EQNMARK_UNDER, + EQNMARK__MAX +}; + +enum eqn_fontt { + EQNFONT_NONE = 0, + EQNFONT_ROMAN, + EQNFONT_BOLD, + EQNFONT_FAT, + EQNFONT_ITALIC, + EQNFONT__MAX +}; + +enum eqn_post { + EQNPOS_NONE = 0, + EQNPOS_OVER, + EQNPOS_SUP, + EQNPOS_SUB, + EQNPOS_TO, + EQNPOS_FROM, + EQNPOS__MAX +}; + +enum eqn_pilet { + EQNPILE_NONE = 0, + EQNPILE_PILE, + EQNPILE_CPILE, + EQNPILE_RPILE, + EQNPILE_LPILE, + EQNPILE_COL, + EQNPILE_CCOL, + EQNPILE_RCOL, + EQNPILE_LCOL, + EQNPILE__MAX +}; + + /* + * A "box" is a parsed mathematical expression as defined by the eqn.7 + * grammar. + */ +struct eqn_box { + int size; /* font size of expression */ +#define EQN_DEFSIZE INT_MIN + enum eqn_boxt type; /* type of node */ + struct eqn_box *first; /* first child node */ + struct eqn_box *last; /* last child node */ + struct eqn_box *next; /* node sibling */ + struct eqn_box *parent; /* node sibling */ + char *text; /* text (or NULL) */ + char *left; + char *right; + enum eqn_post pos; /* position of next box */ + enum eqn_markt mark; /* a mark about the box */ + enum eqn_fontt font; /* font of box */ + enum eqn_pilet pile; /* equation piling */ +}; + +/* + * An equation consists of a tree of expressions starting at a given + * line and position. + */ +struct eqn { + char *name; /* identifier (or NULL) */ + struct eqn_box *root; /* root mathematical expression */ + int ln; /* invocation line */ + int pos; /* invocation position */ +}; + +/* + * The type of parse sequence. This value is usually passed via the + * mandoc(1) command line of -man and -mdoc. It's almost exclusively + * -mandoc but the others have been retained for compatibility. + */ +enum mparset { + MPARSE_AUTO, /* magically determine the document type */ + MPARSE_MDOC, /* assume -mdoc */ + MPARSE_MAN /* assume -man */ +}; + +enum mandoc_esc { + ESCAPE_ERROR = 0, /* bail! unparsable escape */ + ESCAPE_IGNORE, /* escape to be ignored */ + ESCAPE_SPECIAL, /* a regular special character */ + ESCAPE_FONT, /* a generic font mode */ + ESCAPE_FONTBOLD, /* bold font mode */ + ESCAPE_FONTITALIC, /* italic font mode */ + ESCAPE_FONTROMAN, /* roman font mode */ + ESCAPE_FONTPREV, /* previous font mode */ + ESCAPE_NUMBERED, /* a numbered glyph */ + ESCAPE_UNICODE, /* a unicode codepoint */ + ESCAPE_NOSPACE /* suppress space if the last on a line */ +}; + +typedef void (*mandocmsg)(enum mandocerr, enum mandoclevel, + const char *, int, int, const char *); + +struct mparse; +struct mchars; +struct mdoc; +struct man; + +__BEGIN_DECLS + +void *mandoc_calloc(size_t, size_t); +enum mandoc_esc mandoc_escape(const char **, const char **, int *); +void *mandoc_malloc(size_t); +void *mandoc_realloc(void *, size_t); +char *mandoc_strdup(const char *); +char *mandoc_strndup(const char *, size_t); +struct mchars *mchars_alloc(void); +void mchars_free(struct mchars *); +char mchars_num2char(const char *, size_t); +int mchars_num2uc(const char *, size_t); +int mchars_spec2cp(const struct mchars *, + const char *, size_t); +const char *mchars_spec2str(const struct mchars *, + const char *, size_t, size_t *); +struct mparse *mparse_alloc(enum mparset, + enum mandoclevel, mandocmsg, void *); +void mparse_free(struct mparse *); +void mparse_keep(struct mparse *); +enum mandoclevel mparse_readfd(struct mparse *, int, const char *); +enum mandoclevel mparse_readmem(struct mparse *, const void *, size_t, + const char *); +void mparse_reset(struct mparse *); +void mparse_result(struct mparse *, + struct mdoc **, struct man **); +const char *mparse_getkeep(const struct mparse *); +const char *mparse_strerror(enum mandocerr); +const char *mparse_strlevel(enum mandoclevel); + +__END_DECLS + +#endif /*!MANDOC_H*/ diff --git a/mandoc_char.7 b/mandoc_char.7 new file mode 100644 index 000000000000..acc1b6100d4f --- /dev/null +++ b/mandoc_char.7 @@ -0,0 +1,743 @@ +.\" $Id: mandoc_char.7,v 1.51 2011/11/23 10:09:30 kristaps Exp $ +.\" +.\" Copyright (c) 2003 Jason McIntyre <jmc@openbsd.org> +.\" Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> +.\" Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: November 23 2011 $ +.Dt MANDOC_CHAR 7 +.Os +.Sh NAME +.Nm mandoc_char +.Nd mandoc special characters +.Sh DESCRIPTION +This page documents the +.Xr roff 7 +escape sequences accepted by +.Xr mandoc 1 +to represent special characters in +.Xr mdoc 7 +and +.Xr man 7 +documents. +.Pp +The rendering depends on the +.Xr mandoc 1 +output mode; in ASCII output, most characters are completely +unintelligible. +For that reason, using any of the special characters documented here, +except those discussed in the +.Sx DESCRIPTION , +is strongly discouraged; they are supported merely for backwards +compatibility with existing documents. +.Pp +In particular, in English manual pages, do not use special-character +escape sequences to represent national language characters in author +names; instead, provide ASCII transcriptions of the names. +.Ss Dashes and Hyphens +In typography there are different types of dashes of various width: +the hyphen (-), +the minus sign (\-), +the en-dash (\(en), +and the em-dash (\(em). +.Pp +Hyphens are used for adjectives; +to separate the two parts of a compound word; +or to separate a word across two successive lines of text. +The hyphen does not need to be escaped: +.Bd -unfilled -offset indent +blue-eyed +lorry-driver +.Ed +.Pp +The mathematical minus sign is used for negative numbers or subtraction. +It should be written as +.Sq \e- : +.Bd -unfilled -offset indent +a = 3 \e- 1; +b = \e-2; +.Ed +.Pp +The en-dash is used to separate the two elements of a range, +or can be used the same way as an em-dash. +It should be written as +.Sq \e(en : +.Bd -unfilled -offset indent +pp. 95\e(en97. +Go away \e(en or else! +.Ed +.Pp +The em-dash can be used to show an interruption +or can be used the same way as colons, semi-colons, or parentheses. +It should be written as +.Sq \e(em : +.Bd -unfilled -offset indent +Three things \e(em apples, oranges, and bananas. +This is not that \e(em rather, this is that. +.Ed +.Pp +Note: +hyphens, minus signs, and en-dashes look identical under normal ASCII output. +Other formats, such as PostScript, render them correctly, +with differing widths. +.Ss Spaces +To separate words in normal text, for indenting and alignment +in literal context, and when none of the following special cases apply, +just use the normal space character +.Pq Sq \ . +.Pp +When filling text, lines may be broken between words, i.e. at space +characters. +To prevent a line break between two particular words, +use the non-breaking space escape sequence +.Pq Sq \e~ +instead of the normal space character. +For example, the input string +.Dq number\e~1 +will be kept together as +.Dq number\~1 +on the same output line. +.Pp +On request and macro lines, the normal space character serves as an +argument delimiter. +To include whitespace into arguments, quoting is usually the best choice. +In some cases, using either the non-breaking +.Pq Sq \e~ +or the breaking +.Pq Sq \e\ \& +space escape sequence may be preferable. +To escape macro names and to protect whitespace at the end +of input lines, the zero-width space +.Pq Sq \e& +is often useful. +For example, in +.Xr mdoc 7 , +a normal space character can be displayed in single quotes in either +of the following ways: +.Pp +.Dl .Sq \(dq \(dq +.Dl .Sq \e \e& +.Ss Quotes +On request and macro lines, the double-quote character +.Pq Sq \(dq +is handled specially to allow quoting. +One way to prevent this special handling is by using the +.Sq \e(dq +escape sequence. +.Pp +Note that on text lines, literal double-quote characters can be used +verbatim. +All other quote-like characters can be used verbatim as well, +even on request and macro lines. +.Ss Periods +The period +.Pq Sq \&. +is handled specially at the beginning of an input line, +where it introduces a +.Xr roff 7 +request or a macro, and when appearing alone as a macro argument in +.Xr mdoc 7 . +In such situations, prepend a zero-width space +.Pq Sq \e&. +to make it behave like normal text. +.Pp +Do not use the +.Sq \e. +escape sequence. +It does not prevent special handling of the period. +.Ss Backslashes +To include a literal backslash +.Pq Sq \e +into the output, use the +.Pq Sq \ee +escape sequence. +.Pp +Note that doubling it +.Pq Sq \e\e +is not the right way to output a backslash. +Because +.Xr mandoc 1 +does not implement full +.Xr roff 7 +functionality, it may work with +.Xr mandoc 1 , +but it may have weird effects on complete +.Xr roff 7 +implementations. +.Sh SPECIAL CHARACTERS +Special characters are encoded as +.Sq \eX +.Pq for a one-character escape , +.Sq \e(XX +.Pq two-character , +and +.Sq \e[N] +.Pq N-character . +For details, see the +.Em Special Characters +subsection of the +.Xr roff 7 +manual. +.Pp +Spacing: +.Bl -column "Input" "Description" -offset indent -compact +.It Em Input Ta Em Description +.It \e~ Ta non-breaking, non-collapsing space +.It \e Ta breaking, non-collapsing n-width space +.It \e^ Ta zero-width space +.It \e% Ta zero-width space +.It \e& Ta zero-width space +.It \e| Ta zero-width space +.It \e0 Ta breaking, non-collapsing digit-width space +.It \ec Ta removes any trailing space (if applicable) +.El +.Pp +Lines: +.Bl -column "Input" "Rendered" "Description" -offset indent -compact +.It Em Input Ta Em Rendered Ta Em Description +.It \e(ba Ta \(ba Ta bar +.It \e(br Ta \(br Ta box rule +.It \e(ul Ta \(ul Ta underscore +.It \e(rl Ta \(rl Ta overline +.It \e(bb Ta \(bb Ta broken bar +.It \e(sl Ta \(sl Ta forward slash +.It \e(rs Ta \(rs Ta backward slash +.El +.Pp +Text markers: +.Bl -column "Input" "Rendered" "Description" -offset indent -compact +.It Em Input Ta Em Rendered Ta Em Description +.It \e(ci Ta \(ci Ta circle +.It \e(bu Ta \(bu Ta bullet +.It \e(dd Ta \(dd Ta double dagger +.It \e(dg Ta \(dg Ta dagger +.It \e(lz Ta \(lz Ta lozenge +.It \e(sq Ta \(sq Ta white square +.It \e(ps Ta \(ps Ta paragraph +.It \e(sc Ta \(sc Ta section +.It \e(lh Ta \(lh Ta left hand +.It \e(rh Ta \(rh Ta right hand +.It \e(at Ta \(at Ta at +.It \e(sh Ta \(sh Ta hash (pound) +.It \e(CR Ta \(CR Ta carriage return +.It \e(OK Ta \(OK Ta check mark +.El +.Pp +Legal symbols: +.Bl -column "Input" "Rendered" "Description" -offset indent -compact +.It Em Input Ta Em Rendered Ta Em Description +.It \e(co Ta \(co Ta copyright +.It \e(rg Ta \(rg Ta registered +.It \e(tm Ta \(tm Ta trademarked +.El +.Pp +Punctuation: +.Bl -column "Input" "Rendered" "Description" -offset indent -compact +.It Em Input Ta Em Rendered Ta Em Description +.It \e(em Ta \(em Ta em-dash +.It \e(en Ta \(en Ta en-dash +.It \e(hy Ta \(hy Ta hyphen +.It \ee Ta \e Ta back-slash +.It \e. Ta \. Ta period +.It \e(r! Ta \(r! Ta upside-down exclamation +.It \e(r? Ta \(r? Ta upside-down question +.El +.Pp +Quotes: +.Bl -column "Input" "Rendered" "Description" -offset indent -compact +.It Em Input Ta Em Rendered Ta Em Description +.It \e(Bq Ta \(Bq Ta right low double-quote +.It \e(bq Ta \(bq Ta right low single-quote +.It \e(lq Ta \(lq Ta left double-quote +.It \e(rq Ta \(rq Ta right double-quote +.It \e(oq Ta \(oq Ta left single-quote +.It \e(cq Ta \(cq Ta right single-quote +.It \e(aq Ta \(aq Ta apostrophe quote (text) +.It \e(dq Ta \(dq Ta double quote (text) +.It \e(Fo Ta \(Fo Ta left guillemet +.It \e(Fc Ta \(Fc Ta right guillemet +.It \e(fo Ta \(fo Ta left single guillemet +.It \e(fc Ta \(fc Ta right single guillemet +.El +.Pp +Brackets: +.Bl -column "xxbracketrightbpx" Rendered Description -offset indent -compact +.It Em Input Ta Em Rendered Ta Em Description +.It \e(lB Ta \(lB Ta left bracket +.It \e(rB Ta \(rB Ta right bracket +.It \e(lC Ta \(lC Ta left brace +.It \e(rC Ta \(rC Ta right brace +.It \e(la Ta \(la Ta left angle +.It \e(ra Ta \(ra Ta right angle +.It \e(bv Ta \(bv Ta brace extension +.It \e[braceex] Ta \[braceex] Ta brace extension +.It \e[bracketlefttp] Ta \[bracketlefttp] Ta top-left hooked bracket +.It \e[bracketleftbp] Ta \[bracketleftbp] Ta bottom-left hooked bracket +.It \e[bracketleftex] Ta \[bracketleftex] Ta left hooked bracket extension +.It \e[bracketrighttp] Ta \[bracketrighttp] Ta top-right hooked bracket +.It \e[bracketrightbp] Ta \[bracketrightbp] Ta bottom-right hooked bracket +.It \e[bracketrightex] Ta \[bracketrightex] Ta right hooked bracket extension +.It \e(lt Ta \(lt Ta top-left hooked brace +.It \e[bracelefttp] Ta \[bracelefttp] Ta top-left hooked brace +.It \e(lk Ta \(lk Ta mid-left hooked brace +.It \e[braceleftmid] Ta \[braceleftmid] Ta mid-left hooked brace +.It \e(lb Ta \(lb Ta bottom-left hooked brace +.It \e[braceleftbp] Ta \[braceleftbp] Ta bottom-left hooked brace +.It \e[braceleftex] Ta \[braceleftex] Ta left hooked brace extension +.It \e(rt Ta \(rt Ta top-left hooked brace +.It \e[bracerighttp] Ta \[bracerighttp] Ta top-right hooked brace +.It \e(rk Ta \(rk Ta mid-right hooked brace +.It \e[bracerightmid] Ta \[bracerightmid] Ta mid-right hooked brace +.It \e(rb Ta \(rb Ta bottom-right hooked brace +.It \e[bracerightbp] Ta \[bracerightbp] Ta bottom-right hooked brace +.It \e[bracerightex] Ta \[bracerightex] Ta right hooked brace extension +.It \e[parenlefttp] Ta \[parenlefttp] Ta top-left hooked parenthesis +.It \e[parenleftbp] Ta \[parenleftbp] Ta bottom-left hooked parenthesis +.It \e[parenleftex] Ta \[parenleftex] Ta left hooked parenthesis extension +.It \e[parenrighttp] Ta \[parenrighttp] Ta top-right hooked parenthesis +.It \e[parenrightbp] Ta \[parenrightbp] Ta bottom-right hooked parenthesis +.It \e[parenrightex] Ta \[parenrightex] Ta right hooked parenthesis extension +.El +.Pp +Arrows: +.Bl -column "Input" "Rendered" "Description" -offset indent -compact +.It Em Input Ta Em Rendered Ta Em Description +.It \e(<- Ta \(<- Ta left arrow +.It \e(-> Ta \(-> Ta right arrow +.It \e(<> Ta \(<> Ta left-right arrow +.It \e(da Ta \(da Ta down arrow +.It \e(ua Ta \(ua Ta up arrow +.It \e(va Ta \(va Ta up-down arrow +.It \e(lA Ta \(lA Ta left double-arrow +.It \e(rA Ta \(rA Ta right double-arrow +.It \e(hA Ta \(hA Ta left-right double-arrow +.It \e(uA Ta \(uA Ta up double-arrow +.It \e(dA Ta \(dA Ta down double-arrow +.It \e(vA Ta \(vA Ta up-down double-arrow +.El +.Pp +Logical: +.Bl -column "Input" "Rendered" "Description" -offset indent -compact +.It Em Input Ta Em Rendered Ta Em Description +.It \e(AN Ta \(AN Ta logical and +.It \e(OR Ta \(OR Ta logical or +.It \e(no Ta \(no Ta logical not +.It \e[tno] Ta \[tno] Ta logical not (text) +.It \e(te Ta \(te Ta existential quantifier +.It \e(fa Ta \(fa Ta universal quantifier +.It \e(st Ta \(st Ta such that +.It \e(tf Ta \(tf Ta therefore +.It \e(3d Ta \(3d Ta therefore +.It \e(or Ta \(or Ta bitwise or +.El +.Pp +Mathematical: +.Bl -column "xxcoproductxx" "Rendered" "Description" -offset indent -compact +.It Em Input Ta Em Rendered Ta Em Description +.It \e(pl Ta \(pl Ta plus +.It \e(mi Ta \(mi Ta minus +.It \e- Ta \- Ta minus (text) +.It \e(-+ Ta \(-+ Ta minus-plus +.It \e(+- Ta \(+- Ta plus-minus +.It \e[t+-] Ta \[t+-] Ta plus-minus (text) +.It \e(pc Ta \(pc Ta centre-dot +.It \e(mu Ta \(mu Ta multiply +.It \e[tmu] Ta \[tmu] Ta multiply (text) +.It \e(c* Ta \(c* Ta circle-multiply +.It \e(c+ Ta \(c+ Ta circle-plus +.It \e(di Ta \(di Ta divide +.It \e[tdi] Ta \[tdi] Ta divide (text) +.It \e(f/ Ta \(f/ Ta fraction +.It \e(** Ta \(** Ta asterisk +.It \e(<= Ta \(<= Ta less-than-equal +.It \e(>= Ta \(>= Ta greater-than-equal +.It \e(<< Ta \(<< Ta much less +.It \e(>> Ta \(>> Ta much greater +.It \e(eq Ta \(eq Ta equal +.It \e(!= Ta \(!= Ta not equal +.It \e(== Ta \(== Ta equivalent +.It \e(ne Ta \(ne Ta not equivalent +.It \e(=~ Ta \(=~ Ta congruent +.It \e(-~ Ta \(-~ Ta asymptotically congruent +.It \e(ap Ta \(ap Ta asymptotically similar +.It \e(~~ Ta \(~~ Ta approximately similar +.It \e(~= Ta \(~= Ta approximately equal +.It \e(pt Ta \(pt Ta proportionate +.It \e(es Ta \(es Ta empty set +.It \e(mo Ta \(mo Ta element +.It \e(nm Ta \(nm Ta not element +.It \e(sb Ta \(sb Ta proper subset +.It \e(nb Ta \(nb Ta not subset +.It \e(sp Ta \(sp Ta proper superset +.It \e(nc Ta \(nc Ta not superset +.It \e(ib Ta \(ib Ta reflexive subset +.It \e(ip Ta \(ip Ta reflexive superset +.It \e(ca Ta \(ca Ta intersection +.It \e(cu Ta \(cu Ta union +.It \e(/_ Ta \(/_ Ta angle +.It \e(pp Ta \(pp Ta perpendicular +.It \e(is Ta \(is Ta integral +.It \e[integral] Ta \[integral] Ta integral +.It \e[sum] Ta \[sum] Ta summation +.It \e[product] Ta \[product] Ta product +.It \e[coproduct] Ta \[coproduct] Ta coproduct +.It \e(gr Ta \(gr Ta gradient +.It \e(sr Ta \(sr Ta square root +.It \e[sqrt] Ta \[sqrt] Ta square root +.It \e(lc Ta \(lc Ta left-ceiling +.It \e(rc Ta \(rc Ta right-ceiling +.It \e(lf Ta \(lf Ta left-floor +.It \e(rf Ta \(rf Ta right-floor +.It \e(if Ta \(if Ta infinity +.It \e(Ah Ta \(Ah Ta aleph +.It \e(Im Ta \(Im Ta imaginary +.It \e(Re Ta \(Re Ta real +.It \e(pd Ta \(pd Ta partial differential +.It \e(-h Ta \(-h Ta Planck constant over 2\(*p +.It \e[12] Ta \[12] Ta one-half +.It \e[14] Ta \[14] Ta one-fourth +.It \e[34] Ta \[34] Ta three-fourths +.El +.Pp +Ligatures: +.Bl -column "Input" "Rendered" "Description" -offset indent -compact +.It Em Input Ta Em Rendered Ta Em Description +.It \e(ff Ta \(ff Ta ff ligature +.It \e(fi Ta \(fi Ta fi ligature +.It \e(fl Ta \(fl Ta fl ligature +.It \e(Fi Ta \(Fi Ta ffi ligature +.It \e(Fl Ta \(Fl Ta ffl ligature +.It \e(AE Ta \(AE Ta AE +.It \e(ae Ta \(ae Ta ae +.It \e(OE Ta \(OE Ta OE +.It \e(oe Ta \(oe Ta oe +.It \e(ss Ta \(ss Ta German eszett +.It \e(IJ Ta \(IJ Ta IJ ligature +.It \e(ij Ta \(ij Ta ij ligature +.El +.Pp +Accents: +.Bl -column "Input" "Rendered" "Description" -offset indent -compact +.It Em Input Ta Em Rendered Ta Em Description +.It \e(a" Ta \(a" Ta Hungarian umlaut +.It \e(a- Ta \(a- Ta macron +.It \e(a. Ta \(a. Ta dotted +.It \e(a^ Ta \(a^ Ta circumflex +.It \e(aa Ta \(aa Ta acute +.It \e' Ta \' Ta acute +.It \e(ga Ta \(ga Ta grave +.It \e` Ta \` Ta grave +.It \e(ab Ta \(ab Ta breve +.It \e(ac Ta \(ac Ta cedilla +.It \e(ad Ta \(ad Ta dieresis +.It \e(ah Ta \(ah Ta caron +.It \e(ao Ta \(ao Ta ring +.It \e(a~ Ta \(a~ Ta tilde +.It \e(ho Ta \(ho Ta ogonek +.It \e(ha Ta \(ha Ta hat (text) +.It \e(ti Ta \(ti Ta tilde (text) +.El +.Pp +Accented letters: +.Bl -column "Input" "Rendered" "Description" -offset indent -compact +.It Em Input Ta Em Rendered Ta Em Description +.It \e('A Ta \('A Ta acute A +.It \e('E Ta \('E Ta acute E +.It \e('I Ta \('I Ta acute I +.It \e('O Ta \('O Ta acute O +.It \e('U Ta \('U Ta acute U +.It \e('a Ta \('a Ta acute a +.It \e('e Ta \('e Ta acute e +.It \e('i Ta \('i Ta acute i +.It \e('o Ta \('o Ta acute o +.It \e('u Ta \('u Ta acute u +.It \e(`A Ta \(`A Ta grave A +.It \e(`E Ta \(`E Ta grave E +.It \e(`I Ta \(`I Ta grave I +.It \e(`O Ta \(`O Ta grave O +.It \e(`U Ta \(`U Ta grave U +.It \e(`a Ta \(`a Ta grave a +.It \e(`e Ta \(`e Ta grave e +.It \e(`i Ta \(`i Ta grave i +.It \e(`o Ta \(`i Ta grave o +.It \e(`u Ta \(`u Ta grave u +.It \e(~A Ta \(~A Ta tilde A +.It \e(~N Ta \(~N Ta tilde N +.It \e(~O Ta \(~O Ta tilde O +.It \e(~a Ta \(~a Ta tilde a +.It \e(~n Ta \(~n Ta tilde n +.It \e(~o Ta \(~o Ta tilde o +.It \e(:A Ta \(:A Ta dieresis A +.It \e(:E Ta \(:E Ta dieresis E +.It \e(:I Ta \(:I Ta dieresis I +.It \e(:O Ta \(:O Ta dieresis O +.It \e(:U Ta \(:U Ta dieresis U +.It \e(:a Ta \(:a Ta dieresis a +.It \e(:e Ta \(:e Ta dieresis e +.It \e(:i Ta \(:i Ta dieresis i +.It \e(:o Ta \(:o Ta dieresis o +.It \e(:u Ta \(:u Ta dieresis u +.It \e(:y Ta \(:y Ta dieresis y +.It \e(^A Ta \(^A Ta circumflex A +.It \e(^E Ta \(^E Ta circumflex E +.It \e(^I Ta \(^I Ta circumflex I +.It \e(^O Ta \(^O Ta circumflex O +.It \e(^U Ta \(^U Ta circumflex U +.It \e(^a Ta \(^a Ta circumflex a +.It \e(^e Ta \(^e Ta circumflex e +.It \e(^i Ta \(^i Ta circumflex i +.It \e(^o Ta \(^o Ta circumflex o +.It \e(^u Ta \(^u Ta circumflex u +.It \e(,C Ta \(,C Ta cedilla C +.It \e(,c Ta \(,c Ta cedilla c +.It \e(/L Ta \(/L Ta stroke L +.It \e(/l Ta \(/l Ta stroke l +.It \e(/O Ta \(/O Ta stroke O +.It \e(/o Ta \(/o Ta stroke o +.It \e(oA Ta \(oA Ta ring A +.It \e(oa Ta \(oa Ta ring a +.El +.Pp +Special letters: +.Bl -column "Input" "Rendered" "Description" -offset indent -compact +.It Em Input Ta Em Rendered Ta Em Description +.It \e(-D Ta \(-D Ta Eth +.It \e(Sd Ta \(Sd Ta eth +.It \e(TP Ta \(TP Ta Thorn +.It \e(Tp Ta \(Tp Ta thorn +.It \e(.i Ta \(.i Ta dotless i +.It \e(.j Ta \(.j Ta dotless j +.El +.Pp +Currency: +.Bl -column "Input" "Rendered" "Description" -offset indent -compact +.It Em Input Ta Em Rendered Ta Em Description +.It \e(Do Ta \(Do Ta dollar +.It \e(ct Ta \(ct Ta cent +.It \e(Eu Ta \(Eu Ta Euro symbol +.It \e(eu Ta \(eu Ta Euro symbol +.It \e(Ye Ta \(Ye Ta yen +.It \e(Po Ta \(Po Ta pound +.It \e(Cs Ta \(Cs Ta Scandinavian +.It \e(Fn Ta \(Fn Ta florin +.El +.Pp +Units: +.Bl -column "Input" "Rendered" "Description" -offset indent -compact +.It Em Input Ta Em Rendered Ta Em Description +.It \e(de Ta \(de Ta degree +.It \e(%0 Ta \(%0 Ta per-thousand +.It \e(fm Ta \(fm Ta minute +.It \e(sd Ta \(sd Ta second +.It \e(mc Ta \(mc Ta micro +.El +.Pp +Greek letters: +.Bl -column "Input" "Rendered" "Description" -offset indent -compact +.It Em Input Ta Em Rendered Ta Em Description +.It \e(*A Ta \(*A Ta Alpha +.It \e(*B Ta \(*B Ta Beta +.It \e(*G Ta \(*G Ta Gamma +.It \e(*D Ta \(*D Ta Delta +.It \e(*E Ta \(*E Ta Epsilon +.It \e(*Z Ta \(*Z Ta Zeta +.It \e(*Y Ta \(*Y Ta Eta +.It \e(*H Ta \(*H Ta Theta +.It \e(*I Ta \(*I Ta Iota +.It \e(*K Ta \(*K Ta Kappa +.It \e(*L Ta \(*L Ta Lambda +.It \e(*M Ta \(*M Ta Mu +.It \e(*N Ta \(*N Ta Nu +.It \e(*C Ta \(*C Ta Xi +.It \e(*O Ta \(*O Ta Omicron +.It \e(*P Ta \(*P Ta Pi +.It \e(*R Ta \(*R Ta Rho +.It \e(*S Ta \(*S Ta Sigma +.It \e(*T Ta \(*T Ta Tau +.It \e(*U Ta \(*U Ta Upsilon +.It \e(*F Ta \(*F Ta Phi +.It \e(*X Ta \(*X Ta Chi +.It \e(*Q Ta \(*Q Ta Psi +.It \e(*W Ta \(*W Ta Omega +.It \e(*a Ta \(*a Ta alpha +.It \e(*b Ta \(*b Ta beta +.It \e(*g Ta \(*g Ta gamma +.It \e(*d Ta \(*d Ta delta +.It \e(*e Ta \(*e Ta epsilon +.It \e(*z Ta \(*z Ta zeta +.It \e(*y Ta \(*y Ta eta +.It \e(*h Ta \(*h Ta theta +.It \e(*i Ta \(*i Ta iota +.It \e(*k Ta \(*k Ta kappa +.It \e(*l Ta \(*l Ta lambda +.It \e(*m Ta \(*m Ta mu +.It \e(*n Ta \(*n Ta nu +.It \e(*c Ta \(*c Ta xi +.It \e(*o Ta \(*o Ta omicron +.It \e(*p Ta \(*p Ta pi +.It \e(*r Ta \(*r Ta rho +.It \e(*s Ta \(*s Ta sigma +.It \e(*t Ta \(*t Ta tau +.It \e(*u Ta \(*u Ta upsilon +.It \e(*f Ta \(*f Ta phi +.It \e(*x Ta \(*x Ta chi +.It \e(*q Ta \(*q Ta psi +.It \e(*w Ta \(*w Ta omega +.It \e(+h Ta \(+h Ta theta variant +.It \e(+f Ta \(+f Ta phi variant +.It \e(+p Ta \(+p Ta pi variant +.It \e(+e Ta \(+e Ta epsilon variant +.It \e(ts Ta \(ts Ta sigma terminal +.El +.Sh PREDEFINED STRINGS +Predefined strings are inherited from the macro packages of historical +troff implementations. +They are +.Em not recommended +for use, as they differ across implementations. +Manuals using these predefined strings are almost certainly not +portable. +.Pp +Their syntax is similar to special characters, using +.Sq \e*X +.Pq for a one-character escape , +.Sq \e*(XX +.Pq two-character , +and +.Sq \e*[N] +.Pq N-character . +For details, see the +.Em Predefined Strings +subsection of the +.Xr roff 7 +manual. +.Bl -column "Input" "Rendered" "Description" -offset indent +.It Em Input Ta Em Rendered Ta Em Description +.It \e*(Ba Ta \*(Ba Ta vertical bar +.It \e*(Ne Ta \*(Ne Ta not equal +.It \e*(Ge Ta \*(Ge Ta greater-than-equal +.It \e*(Le Ta \*(Le Ta less-than-equal +.It \e*(Gt Ta \*(Gt Ta greater-than +.It \e*(Lt Ta \*(Lt Ta less-than +.It \e*(Pm Ta \*(Pm Ta plus-minus +.It \e*(If Ta \*(If Ta infinity +.It \e*(Pi Ta \*(Pi Ta pi +.It \e*(Na Ta \*(Na Ta NaN +.It \e*(Am Ta \*(Am Ta ampersand +.It \e*R Ta \*R Ta restricted mark +.It \e*(Tm Ta \*(Tm Ta trade mark +.It \e*q Ta \*q Ta double-quote +.It \e*(Rq Ta \*(Rq Ta right-double-quote +.It \e*(Lq Ta \*(Lq Ta left-double-quote +.It \e*(lp Ta \*(lp Ta right-parenthesis +.It \e*(rp Ta \*(rp Ta left-parenthesis +.It \e*(lq Ta \*(lq Ta left double-quote +.It \e*(rq Ta \*(rq Ta right double-quote +.It \e*(ua Ta \*(ua Ta up arrow +.It \e*(va Ta \*(va Ta up-down arrow +.It \e*(<= Ta \*(<= Ta less-than-equal +.It \e*(>= Ta \*(>= Ta greater-than-equal +.It \e*(aa Ta \*(aa Ta acute +.It \e*(ga Ta \*(ga Ta grave +.It \e*(Px Ta \*(Px Ta POSIX standard name +.It \e*(Ai Ta \*(Ai Ta ANSI standard name +.El +.Sh UNICODE CHARACTERS +The escape sequence +.Pp +.Dl \e[uXXXX] +.Pp +is interpreted as a Unicode codepoint. +The codepoint must be in the range above U+0080 and less than U+10FFFF. +For compatibility, points must be zero-padded to four characters; if +greater than four characters, no zero padding is allowed. +Unicode surrogates are not allowed. +.\" .Pp +.\" Unicode glyphs attenuate to the +.\" .Sq \&? +.\" character if invalid or not rendered by current output media. +.Sh NUMBERED CHARACTERS +For backward compatibility with existing manuals, +.Xr mandoc 1 +also supports the +.Pp +.Dl \eN\(aq Ns Ar number Ns \(aq +.Pp +escape sequence, inserting the character +.Ar number +from the current character set into the output. +Of course, this is inherently non-portable and is already marked +as deprecated in the Heirloom roff manual. +For example, do not use \eN'34', use \e(dq, or even the plain +.Sq \(dq +character where possible. +.Sh COMPATIBILITY +This section documents compatibility between mandoc and other other +troff implementations, at this time limited to GNU troff +.Pq Qq groff . +.Pp +.Bl -dash -compact +.It +The \eN\(aq\(aq escape sequence is limited to printable characters; in +groff, it accepts arbitrary character numbers. +.It +In +.Fl T Ns Cm ascii , +the +\e(ss, \e(nm, \e(nb, \e(nc, \e(ib, \e(ip, \e(pp, \e[sum], \e[product], +\e[coproduct], \e(gr, \e(\-h, and \e(a. special characters render +differently between mandoc and groff. +.It +In +.Fl T Ns Cm html +and +.Fl T Ns Cm xhtml , +the \e(~=, \e(nb, and \e(nc special characters render differently +between mandoc and groff. +.It +The +.Fl T Ns Cm ps +and +.Fl T Ns Cm pdf +modes format like +.Fl T Ns Cm ascii +instead of rendering glyphs as in groff. +.It +The \e[radicalex], \e[sqrtex], and \e(ru special characters have been omitted +from mandoc either because they are poorly documented or they have no +known representation. +.El +.Sh SEE ALSO +.Xr mandoc 1 , +.Xr man 7 , +.Xr mdoc 7 , +.Xr roff 7 +.Sh AUTHORS +The +.Nm +manual page was written by +.An Kristaps Dzonsons , +.Mt kristaps@bsd.lv . +.Sh CAVEATS +The +.Sq \e*(Ba +escape mimics the behaviour of the +.Sq \&| +character in +.Xr mdoc 7 ; +thus, if you wish to render a vertical bar with no side effects, use +the +.Sq \e(ba +escape. diff --git a/mandocdb.8 b/mandocdb.8 new file mode 100644 index 000000000000..cb48359b3407 --- /dev/null +++ b/mandocdb.8 @@ -0,0 +1,293 @@ +.\" $Id: mandocdb.8,v 1.17 2011/12/25 21:00:23 schwarze Exp $ +.\" +.\" Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: December 25 2011 $ +.Dt MANDOCDB 8 +.Os +.Sh NAME +.Nm mandocdb +.Nd index UNIX manuals +.Sh SYNOPSIS +.Nm +.Op Fl avW +.Op Fl C Ar file +.Nm +.Op Fl avW +.Ar dir ... +.Nm +.Op Fl vW +.Fl d Ar dir +.Op Ar +.Nm +.Op Fl vW +.Fl u Ar dir +.Op Ar +.Nm +.Fl t Ar +.Sh DESCRIPTION +The +.Nm +utility extracts keywords from +.Ux +manuals and indexes them in a +.Sx Keyword Database +and +.Sx Index Database +for fast retrieval by +.Xr apropos 1 , +.Xr whatis 1 , +and +.Xr man 1 Ns 's +.Fl k +option. +.Pp +By default, +.Nm +creates databases in each +.Ar dir +using the files +.Sm off +.Sy man Ar section Li / +.Op Ar arch Li / +.Ar title . section +.Sm on +and +.Sm off +.Sy cat Ar section Li / +.Op Ar arch Li / +.Ar title . Sy 0 +.Sm on +in that directory; +existing databases are truncated. +If +.Ar dir +is not provided, +.Nm +uses the default paths stipulated by +.Xr man 1 . +.Pp +The arguments are as follows: +.Bl -tag -width "-C file" +.It Fl a +Use all directories and files found below +.Ar dir ... . +.It Fl C Ar file +Specify an alternative configuration +.Ar file +in +.Xr man.conf 5 +format. +.It Fl d Ar dir +Merge (remove and re-add) +.Ar +to the database in +.Ar dir +without truncating it. +.It Fl t Ar +Check the given +.Ar files +for potential problems. +No databases are modified. +Implies +.Fl a +and +.Fl W . +All diagnostic messages are printed to the standard output; +the standard error output is not used. +.It Fl u Ar dir +Remove +.Ar +from the database in +.Ar dir +without truncating it. +.It Fl v +Display all files added or removed to the index. +.It Fl W +Print warnings about potential problems with manual pages +to the standard error output. +.El +.Pp +If fatal parse errors are encountered while parsing, the offending file +is printed to stderr, omitted from the index, and the parse continues +with the next input file. +.Ss Index Database +The index database, +.Pa whatis.index , +is a +.Xr recno 3 +database with record values consisting of +.Pp +.Bl -enum -compact +.It +the character +.Cm d , +.Cm a , +or +.Cm c +to indicate the file type +.Po +.Xr mdoc 7 , +.Xr man 7 , +and post-formatted, respectively +.Pc , +.It +the filename relative to the databases' path, +.It +the manual section, +.It +the manual title, +.It +the architecture +.Pq often empty , +.It +and the description. +.El +.Pp +Each of the above is NUL-terminated. +.Pp +If the record value is zero-length, it is unassigned. +.Ss Keyword Database +The keyword database, +.Pa whatis.db , +is a +.Xr btree 3 +database of NUL-terminated keywords (record length is non-zero string +length plus one) mapping to a 16-byte binary field consisting of the +64-bit keyword type and the 64-bit +.Sx Index Database +record number, both in network-byte order. +.Pp +The type bit-mask consists of the following +values mapping into +.Xr mdoc 7 +macro identifiers: +.Pp +.Bl -column "x0x0000000000000001ULLx" "xLix" -offset indent -compact +.It Li 0x0000000000000001ULL Ta \&An +.It Li 0x0000000000000002ULL Ta \&Ar +.It Li 0x0000000000000004ULL Ta \&At +.It Li 0x0000000000000008ULL Ta \&Bsx +.It Li 0x0000000000000010ULL Ta \&Bx +.It Li 0x0000000000000020ULL Ta \&Cd +.It Li 0x0000000000000040ULL Ta \&Cm +.It Li 0x0000000000000080ULL Ta \&Dv +.It Li 0x0000000000000100ULL Ta \&Dx +.It Li 0x0000000000000200ULL Ta \&Em +.It Li 0x0000000000000400ULL Ta \&Er +.It Li 0x0000000000000800ULL Ta \&Ev +.It Li 0x0000000000001000ULL Ta \&Fa +.It Li 0x0000000000002000ULL Ta \&Fl +.It Li 0x0000000000004000ULL Ta \&Fn +.It Li 0x0000000000008000ULL Ta \&Ft +.It Li 0x0000000000010000ULL Ta \&Fx +.It Li 0x0000000000020000ULL Ta \&Ic +.It Li 0x0000000000040000ULL Ta \&In +.It Li 0x0000000000080000ULL Ta \&Lb +.It Li 0x0000000000100000ULL Ta \&Li +.It Li 0x0000000000200000ULL Ta \&Lk +.It Li 0x0000000000400000ULL Ta \&Ms +.It Li 0x0000000000800000ULL Ta \&Mt +.It Li 0x0000000001000000ULL Ta \&Nd +.It Li 0x0000000002000000ULL Ta \&Nm +.It Li 0x0000000004000000ULL Ta \&Nx +.It Li 0x0000000008000000ULL Ta \&Ox +.It Li 0x0000000010000000ULL Ta \&Pa +.It Li 0x0000000020000000ULL Ta \&Rs +.It Li 0x0000000040000000ULL Ta \&Sh +.It Li 0x0000000080000000ULL Ta \&Ss +.It Li 0x0000000100000000ULL Ta \&St +.It Li 0x0000000200000000ULL Ta \&Sy +.It Li 0x0000000400000000ULL Ta \&Tn +.It Li 0x0000000800000000ULL Ta \&Va +.It Li 0x0000001000000000ULL Ta \&Vt +.It Li 0x0000002000000000ULL Ta \&Xr +.El +.Sh IMPLEMENTATION NOTES +The time to construct a new database pair grows linearly with the +number of keywords in the input files. +However, removing or updating entries with +.Fl u +or +.Fl d , +respectively, grows as a multiple of the index length and input size. +.Sh FILES +.Bl -tag -width Ds +.It Pa whatis.db +A +.Xr btree 3 +keyword database mapping keywords to a type and file reference in +.Pa whatis.index . +.It Pa whatis.index +A +.Xr recno 3 +database of indexed file-names. +.It Pa /etc/man.conf +The default +.Xr man 1 +configuration file. +.El +.Sh EXIT STATUS +The +.Nm +utility exits with one of the following values: +.Pp +.Bl -tag -width Ds -compact +.It 0 +No errors occurred. +.It 5 +Invalid command line arguments were specified. +No input files have been read. +.It 6 +An operating system error occurred, for example memory exhaustion or an +error accessing input files. +Such errors cause +.Nm +to exit at once, possibly in the middle of parsing or formatting a file. +The output databases are corrupt and should be removed. +.El +.Sh DIAGNOSTICS +If the following errors occur, the +.Nm +databases should be rebuilt. +.Bl -diag +.It "%s: Corrupt database" +The keyword database file indicated by +.Pa %s +is unreadable. +.It "%s: Corrupt index" +The index database file indicated by +.Pa %s +is unreadable. +.It "%s: Path too long" +The file +.Pa %s +is too long. +This usually indicates database corruption or invalid command-line +arguments. +.El +.Sh SEE ALSO +.Xr apropos 1 , +.Xr man 1 , +.Xr whatis 1 , +.Xr btree 3 , +.Xr recno 3 , +.Xr man.conf 5 +.Sh AUTHORS +The +.Nm +utility was written by +.An Kristaps Dzonsons , +.Mt kristaps@bsd.lv . diff --git a/mandocdb.c b/mandocdb.c new file mode 100644 index 000000000000..e621c1d2a211 --- /dev/null +++ b/mandocdb.c @@ -0,0 +1,1909 @@ +/* $Id: mandocdb.c,v 1.46 2012/03/23 06:52:17 kristaps Exp $ */ +/* + * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/param.h> +#include <sys/types.h> + +#include <assert.h> +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#if defined(__linux__) +# include <endian.h> +# include <db_185.h> +#elif defined(__APPLE__) +# include <libkern/OSByteOrder.h> +# include <db.h> +#else +# include <db.h> +#endif + +#include "man.h" +#include "mdoc.h" +#include "mandoc.h" +#include "mandocdb.h" +#include "manpath.h" + +#define MANDOC_BUFSZ BUFSIZ +#define MANDOC_SLOP 1024 + +#define MANDOC_SRC 0x1 +#define MANDOC_FORM 0x2 + +#define WARNING(_f, _b, _fmt, _args...) \ + do if (warnings) { \ + fprintf(stderr, "%s: ", (_b)); \ + fprintf(stderr, (_fmt), ##_args); \ + if ('\0' != *(_f)) \ + fprintf(stderr, ": %s", (_f)); \ + fprintf(stderr, "\n"); \ + } while (/* CONSTCOND */ 0) + +/* Access to the mandoc database on disk. */ + +struct mdb { + char idxn[MAXPATHLEN]; /* index db filename */ + char dbn[MAXPATHLEN]; /* keyword db filename */ + DB *idx; /* index recno database */ + DB *db; /* keyword btree database */ +}; + +/* Stack of temporarily unused index records. */ + +struct recs { + recno_t *stack; /* pointer to a malloc'ed array */ + size_t size; /* number of allocated slots */ + size_t cur; /* current number of empty records */ + recno_t last; /* last record number in the index */ +}; + +/* Tiny list for files. No need to bring in QUEUE. */ + +struct of { + char *fname; /* heap-allocated */ + char *sec; + char *arch; + char *title; + int src_form; + struct of *next; /* NULL for last one */ + struct of *first; /* first in list */ +}; + +/* Buffer for storing growable data. */ + +struct buf { + char *cp; + size_t len; /* current length */ + size_t size; /* total buffer size */ +}; + +/* Operation we're going to perform. */ + +enum op { + OP_DEFAULT = 0, /* new dbs from dir list or default config */ + OP_CONFFILE, /* new databases from custom config file */ + OP_UPDATE, /* delete/add entries in existing database */ + OP_DELETE, /* delete entries from existing database */ + OP_TEST /* change no databases, report potential problems */ +}; + +#define MAN_ARGS DB *hash, \ + struct buf *buf, \ + struct buf *dbuf, \ + const struct man_node *n +#define MDOC_ARGS DB *hash, \ + struct buf *buf, \ + struct buf *dbuf, \ + const struct mdoc_node *n, \ + const struct mdoc_meta *m + +static void buf_appendmdoc(struct buf *, + const struct mdoc_node *, int); +static void buf_append(struct buf *, const char *); +static void buf_appendb(struct buf *, + const void *, size_t); +static void dbt_put(DB *, const char *, DBT *, DBT *); +static void hash_put(DB *, const struct buf *, uint64_t); +static void hash_reset(DB **); +static void index_merge(const struct of *, struct mparse *, + struct buf *, struct buf *, DB *, + struct mdb *, struct recs *, + const char *); +static void index_prune(const struct of *, struct mdb *, + struct recs *, const char *); +static void ofile_argbuild(int, char *[], + struct of **, const char *); +static void ofile_dirbuild(const char *, const char *, + const char *, int, struct of **, char *); +static void ofile_free(struct of *); +static void pformatted(DB *, struct buf *, struct buf *, + const struct of *, const char *); +static int pman_node(MAN_ARGS); +static void pmdoc_node(MDOC_ARGS); +static int pmdoc_head(MDOC_ARGS); +static int pmdoc_body(MDOC_ARGS); +static int pmdoc_Fd(MDOC_ARGS); +static int pmdoc_In(MDOC_ARGS); +static int pmdoc_Fn(MDOC_ARGS); +static int pmdoc_Nd(MDOC_ARGS); +static int pmdoc_Nm(MDOC_ARGS); +static int pmdoc_Sh(MDOC_ARGS); +static int pmdoc_St(MDOC_ARGS); +static int pmdoc_Xr(MDOC_ARGS); + +#define MDOCF_CHILD 0x01 /* Automatically index child nodes. */ + +struct mdoc_handler { + int (*fp)(MDOC_ARGS); /* Optional handler. */ + uint64_t mask; /* Set unless handler returns 0. */ + int flags; /* For use by pmdoc_node. */ +}; + +static const struct mdoc_handler mdocs[MDOC_MAX] = { + { NULL, 0, 0 }, /* Ap */ + { NULL, 0, 0 }, /* Dd */ + { NULL, 0, 0 }, /* Dt */ + { NULL, 0, 0 }, /* Os */ + { pmdoc_Sh, TYPE_Sh, MDOCF_CHILD }, /* Sh */ + { pmdoc_head, TYPE_Ss, MDOCF_CHILD }, /* Ss */ + { NULL, 0, 0 }, /* Pp */ + { NULL, 0, 0 }, /* D1 */ + { NULL, 0, 0 }, /* Dl */ + { NULL, 0, 0 }, /* Bd */ + { NULL, 0, 0 }, /* Ed */ + { NULL, 0, 0 }, /* Bl */ + { NULL, 0, 0 }, /* El */ + { NULL, 0, 0 }, /* It */ + { NULL, 0, 0 }, /* Ad */ + { NULL, TYPE_An, MDOCF_CHILD }, /* An */ + { NULL, TYPE_Ar, MDOCF_CHILD }, /* Ar */ + { NULL, TYPE_Cd, MDOCF_CHILD }, /* Cd */ + { NULL, TYPE_Cm, MDOCF_CHILD }, /* Cm */ + { NULL, TYPE_Dv, MDOCF_CHILD }, /* Dv */ + { NULL, TYPE_Er, MDOCF_CHILD }, /* Er */ + { NULL, TYPE_Ev, MDOCF_CHILD }, /* Ev */ + { NULL, 0, 0 }, /* Ex */ + { NULL, TYPE_Fa, MDOCF_CHILD }, /* Fa */ + { pmdoc_Fd, TYPE_In, 0 }, /* Fd */ + { NULL, TYPE_Fl, MDOCF_CHILD }, /* Fl */ + { pmdoc_Fn, 0, 0 }, /* Fn */ + { NULL, TYPE_Ft, MDOCF_CHILD }, /* Ft */ + { NULL, TYPE_Ic, MDOCF_CHILD }, /* Ic */ + { pmdoc_In, TYPE_In, 0 }, /* In */ + { NULL, TYPE_Li, MDOCF_CHILD }, /* Li */ + { pmdoc_Nd, TYPE_Nd, MDOCF_CHILD }, /* Nd */ + { pmdoc_Nm, TYPE_Nm, MDOCF_CHILD }, /* Nm */ + { NULL, 0, 0 }, /* Op */ + { NULL, 0, 0 }, /* Ot */ + { NULL, TYPE_Pa, MDOCF_CHILD }, /* Pa */ + { NULL, 0, 0 }, /* Rv */ + { pmdoc_St, TYPE_St, 0 }, /* St */ + { NULL, TYPE_Va, MDOCF_CHILD }, /* Va */ + { pmdoc_body, TYPE_Va, MDOCF_CHILD }, /* Vt */ + { pmdoc_Xr, TYPE_Xr, 0 }, /* Xr */ + { NULL, 0, 0 }, /* %A */ + { NULL, 0, 0 }, /* %B */ + { NULL, 0, 0 }, /* %D */ + { NULL, 0, 0 }, /* %I */ + { NULL, 0, 0 }, /* %J */ + { NULL, 0, 0 }, /* %N */ + { NULL, 0, 0 }, /* %O */ + { NULL, 0, 0 }, /* %P */ + { NULL, 0, 0 }, /* %R */ + { NULL, 0, 0 }, /* %T */ + { NULL, 0, 0 }, /* %V */ + { NULL, 0, 0 }, /* Ac */ + { NULL, 0, 0 }, /* Ao */ + { NULL, 0, 0 }, /* Aq */ + { NULL, TYPE_At, MDOCF_CHILD }, /* At */ + { NULL, 0, 0 }, /* Bc */ + { NULL, 0, 0 }, /* Bf */ + { NULL, 0, 0 }, /* Bo */ + { NULL, 0, 0 }, /* Bq */ + { NULL, TYPE_Bsx, MDOCF_CHILD }, /* Bsx */ + { NULL, TYPE_Bx, MDOCF_CHILD }, /* Bx */ + { NULL, 0, 0 }, /* Db */ + { NULL, 0, 0 }, /* Dc */ + { NULL, 0, 0 }, /* Do */ + { NULL, 0, 0 }, /* Dq */ + { NULL, 0, 0 }, /* Ec */ + { NULL, 0, 0 }, /* Ef */ + { NULL, TYPE_Em, MDOCF_CHILD }, /* Em */ + { NULL, 0, 0 }, /* Eo */ + { NULL, TYPE_Fx, MDOCF_CHILD }, /* Fx */ + { NULL, TYPE_Ms, MDOCF_CHILD }, /* Ms */ + { NULL, 0, 0 }, /* No */ + { NULL, 0, 0 }, /* Ns */ + { NULL, TYPE_Nx, MDOCF_CHILD }, /* Nx */ + { NULL, TYPE_Ox, MDOCF_CHILD }, /* Ox */ + { NULL, 0, 0 }, /* Pc */ + { NULL, 0, 0 }, /* Pf */ + { NULL, 0, 0 }, /* Po */ + { NULL, 0, 0 }, /* Pq */ + { NULL, 0, 0 }, /* Qc */ + { NULL, 0, 0 }, /* Ql */ + { NULL, 0, 0 }, /* Qo */ + { NULL, 0, 0 }, /* Qq */ + { NULL, 0, 0 }, /* Re */ + { NULL, 0, 0 }, /* Rs */ + { NULL, 0, 0 }, /* Sc */ + { NULL, 0, 0 }, /* So */ + { NULL, 0, 0 }, /* Sq */ + { NULL, 0, 0 }, /* Sm */ + { NULL, 0, 0 }, /* Sx */ + { NULL, TYPE_Sy, MDOCF_CHILD }, /* Sy */ + { NULL, TYPE_Tn, MDOCF_CHILD }, /* Tn */ + { NULL, 0, 0 }, /* Ux */ + { NULL, 0, 0 }, /* Xc */ + { NULL, 0, 0 }, /* Xo */ + { pmdoc_head, TYPE_Fn, 0 }, /* Fo */ + { NULL, 0, 0 }, /* Fc */ + { NULL, 0, 0 }, /* Oo */ + { NULL, 0, 0 }, /* Oc */ + { NULL, 0, 0 }, /* Bk */ + { NULL, 0, 0 }, /* Ek */ + { NULL, 0, 0 }, /* Bt */ + { NULL, 0, 0 }, /* Hf */ + { NULL, 0, 0 }, /* Fr */ + { NULL, 0, 0 }, /* Ud */ + { NULL, TYPE_Lb, MDOCF_CHILD }, /* Lb */ + { NULL, 0, 0 }, /* Lp */ + { NULL, TYPE_Lk, MDOCF_CHILD }, /* Lk */ + { NULL, TYPE_Mt, MDOCF_CHILD }, /* Mt */ + { NULL, 0, 0 }, /* Brq */ + { NULL, 0, 0 }, /* Bro */ + { NULL, 0, 0 }, /* Brc */ + { NULL, 0, 0 }, /* %C */ + { NULL, 0, 0 }, /* Es */ + { NULL, 0, 0 }, /* En */ + { NULL, TYPE_Dx, MDOCF_CHILD }, /* Dx */ + { NULL, 0, 0 }, /* %Q */ + { NULL, 0, 0 }, /* br */ + { NULL, 0, 0 }, /* sp */ + { NULL, 0, 0 }, /* %U */ + { NULL, 0, 0 }, /* Ta */ +}; + +static const char *progname; +static int use_all; /* Use all directories and files. */ +static int verb; /* Output verbosity level. */ +static int warnings; /* Potential problems in manuals. */ + +int +main(int argc, char *argv[]) +{ + struct mparse *mp; /* parse sequence */ + struct manpaths dirs; + struct mdb mdb; + struct recs recs; + enum op op; /* current operation */ + const char *dir; + int ch, i, flags; + char dirbuf[MAXPATHLEN]; + DB *hash; /* temporary keyword hashtable */ + BTREEINFO info; /* btree configuration */ + size_t sz1, sz2; + struct buf buf, /* keyword buffer */ + dbuf; /* description buffer */ + struct of *of; /* list of files for processing */ + extern int optind; + extern char *optarg; + + progname = strrchr(argv[0], '/'); + if (progname == NULL) + progname = argv[0]; + else + ++progname; + + memset(&dirs, 0, sizeof(struct manpaths)); + memset(&mdb, 0, sizeof(struct mdb)); + memset(&recs, 0, sizeof(struct recs)); + + of = NULL; + mp = NULL; + hash = NULL; + op = OP_DEFAULT; + dir = NULL; + + while (-1 != (ch = getopt(argc, argv, "aC:d:tu:vW"))) + switch (ch) { + case ('a'): + use_all = 1; + break; + case ('C'): + if (op) { + fprintf(stderr, + "-C: conflicting options\n"); + goto usage; + } + dir = optarg; + op = OP_CONFFILE; + break; + case ('d'): + if (op) { + fprintf(stderr, + "-d: conflicting options\n"); + goto usage; + } + dir = optarg; + op = OP_UPDATE; + break; + case ('t'): + dup2(STDOUT_FILENO, STDERR_FILENO); + if (op) { + fprintf(stderr, + "-t: conflicting options\n"); + goto usage; + } + op = OP_TEST; + use_all = 1; + warnings = 1; + break; + case ('u'): + if (op) { + fprintf(stderr, + "-u: conflicting options\n"); + goto usage; + } + dir = optarg; + op = OP_DELETE; + break; + case ('v'): + verb++; + break; + case ('W'): + warnings = 1; + break; + default: + goto usage; + } + + argc -= optind; + argv += optind; + + if (OP_CONFFILE == op && argc > 0) { + fprintf(stderr, "-C: too many arguments\n"); + goto usage; + } + + memset(&info, 0, sizeof(BTREEINFO)); + info.lorder = 4321; + info.flags = R_DUP; + + mp = mparse_alloc(MPARSE_AUTO, MANDOCLEVEL_FATAL, NULL, NULL); + + memset(&buf, 0, sizeof(struct buf)); + memset(&dbuf, 0, sizeof(struct buf)); + + buf.size = dbuf.size = MANDOC_BUFSZ; + + buf.cp = mandoc_malloc(buf.size); + dbuf.cp = mandoc_malloc(dbuf.size); + + if (OP_TEST == op) { + ofile_argbuild(argc, argv, &of, "."); + if (NULL == of) + goto out; + index_merge(of, mp, &dbuf, &buf, + hash, &mdb, &recs, "."); + goto out; + } + + if (OP_UPDATE == op || OP_DELETE == op) { + strlcat(mdb.dbn, dir, MAXPATHLEN); + strlcat(mdb.dbn, "/", MAXPATHLEN); + sz1 = strlcat(mdb.dbn, MANDOC_DB, MAXPATHLEN); + + strlcat(mdb.idxn, dir, MAXPATHLEN); + strlcat(mdb.idxn, "/", MAXPATHLEN); + sz2 = strlcat(mdb.idxn, MANDOC_IDX, MAXPATHLEN); + + if (sz1 >= MAXPATHLEN || sz2 >= MAXPATHLEN) { + fprintf(stderr, "%s: path too long\n", dir); + exit((int)MANDOCLEVEL_BADARG); + } + + flags = O_CREAT | O_RDWR; + mdb.db = dbopen(mdb.dbn, flags, 0644, DB_BTREE, &info); + mdb.idx = dbopen(mdb.idxn, flags, 0644, DB_RECNO, NULL); + + if (NULL == mdb.db) { + perror(mdb.dbn); + exit((int)MANDOCLEVEL_SYSERR); + } else if (NULL == mdb.idx) { + perror(mdb.idxn); + exit((int)MANDOCLEVEL_SYSERR); + } + + ofile_argbuild(argc, argv, &of, dir); + + if (NULL == of) + goto out; + + index_prune(of, &mdb, &recs, dir); + + /* + * Go to the root of the respective manual tree. + * This must work or no manuals may be found (they're + * indexed relative to the root). + */ + + if (OP_UPDATE == op) { + if (-1 == chdir(dir)) { + perror(dir); + exit((int)MANDOCLEVEL_SYSERR); + } + index_merge(of, mp, &dbuf, &buf, hash, + &mdb, &recs, dir); + } + + goto out; + } + + /* + * Configure the directories we're going to scan. + * If we have command-line arguments, use them. + * If not, we use man(1)'s method (see mandocdb.8). + */ + + if (argc > 0) { + dirs.paths = mandoc_calloc(argc, sizeof(char *)); + dirs.sz = argc; + for (i = 0; i < argc; i++) + dirs.paths[i] = mandoc_strdup(argv[i]); + } else + manpath_parse(&dirs, dir, NULL, NULL); + + for (i = 0; i < dirs.sz; i++) { + /* + * Go to the root of the respective manual tree. + * This must work or no manuals may be found: + * They are indexed relative to the root. + */ + + if (-1 == chdir(dirs.paths[i])) { + perror(dirs.paths[i]); + exit((int)MANDOCLEVEL_SYSERR); + } + + strlcpy(mdb.dbn, MANDOC_DB, MAXPATHLEN); + strlcpy(mdb.idxn, MANDOC_IDX, MAXPATHLEN); + + flags = O_CREAT | O_TRUNC | O_RDWR; + mdb.db = dbopen(mdb.dbn, flags, 0644, DB_BTREE, &info); + mdb.idx = dbopen(mdb.idxn, flags, 0644, DB_RECNO, NULL); + + if (NULL == mdb.db) { + perror(mdb.dbn); + exit((int)MANDOCLEVEL_SYSERR); + } else if (NULL == mdb.idx) { + perror(mdb.idxn); + exit((int)MANDOCLEVEL_SYSERR); + } + + /* + * Search for manuals and fill the new database. + */ + + strlcpy(dirbuf, dirs.paths[i], MAXPATHLEN); + ofile_dirbuild(".", "", "", 0, &of, dirbuf); + + if (NULL != of) { + index_merge(of, mp, &dbuf, &buf, hash, + &mdb, &recs, dirs.paths[i]); + ofile_free(of); + of = NULL; + } + + (*mdb.db->close)(mdb.db); + (*mdb.idx->close)(mdb.idx); + mdb.db = NULL; + mdb.idx = NULL; + } + +out: + if (mdb.db) + (*mdb.db->close)(mdb.db); + if (mdb.idx) + (*mdb.idx->close)(mdb.idx); + if (hash) + (*hash->close)(hash); + if (mp) + mparse_free(mp); + + manpath_free(&dirs); + ofile_free(of); + free(buf.cp); + free(dbuf.cp); + free(recs.stack); + + return(MANDOCLEVEL_OK); + +usage: + fprintf(stderr, + "usage: %s [-av] [-C file] | dir ... | -t file ...\n" + " -d dir [file ...] | " + "-u dir [file ...]\n", + progname); + + return((int)MANDOCLEVEL_BADARG); +} + +void +index_merge(const struct of *of, struct mparse *mp, + struct buf *dbuf, struct buf *buf, DB *hash, + struct mdb *mdb, struct recs *recs, + const char *basedir) +{ + recno_t rec; + int ch, skip; + DBT key, val; + DB *files; /* temporary file name table */ + char emptystring[1] = {'\0'}; + struct mdoc *mdoc; + struct man *man; + char *p; + const char *fn, *msec, *march, *mtitle; + uint64_t mask; + size_t sv; + unsigned seq; + uint64_t vbuf[2]; + char type; + + if (warnings) { + files = NULL; + hash_reset(&files); + } + + rec = 0; + for (of = of->first; of; of = of->next) { + fn = of->fname; + + /* + * Try interpreting the file as mdoc(7) or man(7) + * source code, unless it is already known to be + * formatted. Fall back to formatted mode. + */ + + mparse_reset(mp); + mdoc = NULL; + man = NULL; + + if ((MANDOC_SRC & of->src_form || + ! (MANDOC_FORM & of->src_form)) && + MANDOCLEVEL_FATAL > mparse_readfd(mp, -1, fn)) + mparse_result(mp, &mdoc, &man); + + if (NULL != mdoc) { + msec = mdoc_meta(mdoc)->msec; + march = mdoc_meta(mdoc)->arch; + if (NULL == march) + march = ""; + mtitle = mdoc_meta(mdoc)->title; + } else if (NULL != man) { + msec = man_meta(man)->msec; + march = ""; + mtitle = man_meta(man)->title; + } else { + msec = of->sec; + march = of->arch; + mtitle = of->title; + } + + /* + * Check whether the manual section given in a file + * agrees with the directory where the file is located. + * Some manuals have suffixes like (3p) on their + * section number either inside the file or in the + * directory name, some are linked into more than one + * section, like encrypt(1) = makekey(8). Do not skip + * manuals for such reasons. + */ + + skip = 0; + assert(of->sec); + assert(msec); + if (strcasecmp(msec, of->sec)) + WARNING(fn, basedir, "Section \"%s\" manual " + "in \"%s\" directory", msec, of->sec); + /* + * Manual page directories exist for each kernel + * architecture as returned by machine(1). + * However, many manuals only depend on the + * application architecture as returned by arch(1). + * For example, some (2/ARM) manuals are shared + * across the "armish" and "zaurus" kernel + * architectures. + * A few manuals are even shared across completely + * different architectures, for example fdformat(1) + * on amd64, i386, sparc, and sparc64. + * Thus, warn about architecture mismatches, + * but don't skip manuals for this reason. + */ + + assert(of->arch); + assert(march); + if (strcasecmp(march, of->arch)) + WARNING(fn, basedir, "Architecture \"%s\" " + "manual in \"%s\" directory", + march, of->arch); + + /* + * By default, skip a file if the title given + * in the file disagrees with the file name. + * Do not warn, this happens for all MLINKs. + */ + + assert(of->title); + assert(mtitle); + if (strcasecmp(mtitle, of->title)) + skip = 1; + + /* + * Build a title string for the file. If it matches + * the location of the file, remember the title as + * found; else, remember it as missing. + */ + + if (warnings) { + buf->len = 0; + buf_appendb(buf, mtitle, strlen(mtitle)); + buf_appendb(buf, "(", 1); + buf_appendb(buf, msec, strlen(msec)); + if ('\0' != *march) { + buf_appendb(buf, "/", 1); + buf_appendb(buf, march, strlen(march)); + } + buf_appendb(buf, ")", 2); + for (p = buf->cp; '\0' != *p; p++) + *p = tolower(*p); + key.data = buf->cp; + key.size = buf->len; + val.data = NULL; + val.size = 0; + if (0 == skip) + val.data = emptystring; + else { + ch = (*files->get)(files, &key, &val, 0); + if (ch < 0) { + perror("hash"); + exit((int)MANDOCLEVEL_SYSERR); + } else if (ch > 0) { + val.data = (void *)fn; + val.size = strlen(fn) + 1; + } else + val.data = NULL; + } + if (NULL != val.data && + (*files->put)(files, &key, &val, 0) < 0) { + perror("hash"); + exit((int)MANDOCLEVEL_SYSERR); + } + } + + if (skip && !use_all) + continue; + + /* + * The index record value consists of a nil-terminated + * filename, a nil-terminated manual section, and a + * nil-terminated description. Use the actual + * location of the file, such that the user can find + * it with man(1). Since the description may not be + * set, we set a sentinel to see if we're going to + * write a nil byte in its place. + */ + + dbuf->len = 0; + type = mdoc ? 'd' : (man ? 'a' : 'c'); + buf_appendb(dbuf, &type, 1); + buf_appendb(dbuf, fn, strlen(fn) + 1); + buf_appendb(dbuf, of->sec, strlen(of->sec) + 1); + buf_appendb(dbuf, of->title, strlen(of->title) + 1); + buf_appendb(dbuf, of->arch, strlen(of->arch) + 1); + + sv = dbuf->len; + + /* + * Collect keyword/mask pairs. + * Each pair will become a new btree node. + */ + + hash_reset(&hash); + if (mdoc) + pmdoc_node(hash, buf, dbuf, + mdoc_node(mdoc), mdoc_meta(mdoc)); + else if (man) + pman_node(hash, buf, dbuf, man_node(man)); + else + pformatted(hash, buf, dbuf, of, basedir); + + /* Test mode, do not access any database. */ + + if (NULL == mdb->db || NULL == mdb->idx) + continue; + + /* + * Make sure the file name is always registered + * as an .Nm search key. + */ + buf->len = 0; + buf_append(buf, of->title); + hash_put(hash, buf, TYPE_Nm); + + /* + * Reclaim an empty index record, if available. + * Use its record number for all new btree nodes. + */ + + if (recs->cur > 0) { + recs->cur--; + rec = recs->stack[(int)recs->cur]; + } else if (recs->last > 0) { + rec = recs->last; + recs->last = 0; + } else + rec++; + vbuf[1] = htobe64(rec); + + /* + * Copy from the in-memory hashtable of pending + * keyword/mask pairs into the database. + */ + + seq = R_FIRST; + while (0 == (ch = (*hash->seq)(hash, &key, &val, seq))) { + seq = R_NEXT; + assert(sizeof(uint64_t) == val.size); + memcpy(&mask, val.data, val.size); + vbuf[0] = htobe64(mask); + val.size = sizeof(vbuf); + val.data = &vbuf; + dbt_put(mdb->db, mdb->dbn, &key, &val); + } + if (ch < 0) { + perror("hash"); + exit((int)MANDOCLEVEL_SYSERR); + } + + /* + * Apply to the index. If we haven't had a description + * set, put an empty one in now. + */ + + if (dbuf->len == sv) + buf_appendb(dbuf, "", 1); + + key.data = &rec; + key.size = sizeof(recno_t); + + val.data = dbuf->cp; + val.size = dbuf->len; + + if (verb) + printf("%s: Adding to index: %s\n", basedir, fn); + + dbt_put(mdb->idx, mdb->idxn, &key, &val); + } + + /* + * Iterate the remembered file titles and check that + * all files can be found by their main title. + */ + + if (warnings) { + seq = R_FIRST; + while (0 == (*files->seq)(files, &key, &val, seq)) { + seq = R_NEXT; + if (val.size) + WARNING((char *)val.data, basedir, + "Probably unreachable, title " + "is %s", (char *)key.data); + } + (*files->close)(files); + } +} + +/* + * Scan through all entries in the index file `idx' and prune those + * entries in `ofile'. + * Pruning consists of removing from `db', then invalidating the entry + * in `idx' (zeroing its value size). + */ +static void +index_prune(const struct of *ofile, struct mdb *mdb, + struct recs *recs, const char *basedir) +{ + const struct of *of; + const char *fn; + uint64_t vbuf[2]; + unsigned seq, sseq; + DBT key, val; + int ch; + + recs->cur = 0; + seq = R_FIRST; + while (0 == (ch = (*mdb->idx->seq)(mdb->idx, &key, &val, seq))) { + seq = R_NEXT; + assert(sizeof(recno_t) == key.size); + memcpy(&recs->last, key.data, key.size); + + /* Deleted records are zero-sized. Skip them. */ + + if (0 == val.size) + goto cont; + + /* + * Make sure we're sane. + * Read past our mdoc/man/cat type to the next string, + * then make sure it's bounded by a NUL. + * Failing any of these, we go into our error handler. + */ + + fn = (char *)val.data + 1; + if (NULL == memchr(fn, '\0', val.size - 1)) + break; + + /* + * Search for the file in those we care about. + * XXX: build this into a tree. Too slow. + */ + + for (of = ofile->first; of; of = of->next) + if (0 == strcmp(fn, of->fname)) + break; + + if (NULL == of) + continue; + + /* + * Search through the keyword database, throwing out all + * references to our file. + */ + + sseq = R_FIRST; + while (0 == (ch = (*mdb->db->seq)(mdb->db, + &key, &val, sseq))) { + sseq = R_NEXT; + if (sizeof(vbuf) != val.size) + break; + + memcpy(vbuf, val.data, val.size); + if (recs->last != betoh64(vbuf[1])) + continue; + + if ((ch = (*mdb->db->del)(mdb->db, + &key, R_CURSOR)) < 0) + break; + } + + if (ch < 0) { + perror(mdb->dbn); + exit((int)MANDOCLEVEL_SYSERR); + } else if (1 != ch) { + fprintf(stderr, "%s: corrupt database\n", + mdb->dbn); + exit((int)MANDOCLEVEL_SYSERR); + } + + if (verb) + printf("%s: Deleting from index: %s\n", + basedir, fn); + + val.size = 0; + ch = (*mdb->idx->put)(mdb->idx, &key, &val, R_CURSOR); + + if (ch < 0) + break; +cont: + if (recs->cur >= recs->size) { + recs->size += MANDOC_SLOP; + recs->stack = mandoc_realloc(recs->stack, + recs->size * sizeof(recno_t)); + } + + recs->stack[(int)recs->cur] = recs->last; + recs->cur++; + } + + if (ch < 0) { + perror(mdb->idxn); + exit((int)MANDOCLEVEL_SYSERR); + } else if (1 != ch) { + fprintf(stderr, "%s: corrupt index\n", mdb->idxn); + exit((int)MANDOCLEVEL_SYSERR); + } + + recs->last++; +} + +/* + * Grow the buffer (if necessary) and copy in a binary string. + */ +static void +buf_appendb(struct buf *buf, const void *cp, size_t sz) +{ + + /* Overshoot by MANDOC_BUFSZ. */ + + while (buf->len + sz >= buf->size) { + buf->size = buf->len + sz + MANDOC_BUFSZ; + buf->cp = mandoc_realloc(buf->cp, buf->size); + } + + memcpy(buf->cp + (int)buf->len, cp, sz); + buf->len += sz; +} + +/* + * Append a nil-terminated string to the buffer. + * This can be invoked multiple times. + * The buffer string will be nil-terminated. + * If invoked multiple times, a space is put between strings. + */ +static void +buf_append(struct buf *buf, const char *cp) +{ + size_t sz; + + if (0 == (sz = strlen(cp))) + return; + + if (buf->len) + buf->cp[(int)buf->len - 1] = ' '; + + buf_appendb(buf, cp, sz + 1); +} + +/* + * Recursively add all text from a given node. + * This is optimised for general mdoc nodes in this context, which do + * not consist of subexpressions and having a recursive call for n->next + * would be wasteful. + * The "f" variable should be 0 unless called from pmdoc_Nd for the + * description buffer, which does not start at the beginning of the + * buffer. + */ +static void +buf_appendmdoc(struct buf *buf, const struct mdoc_node *n, int f) +{ + + for ( ; n; n = n->next) { + if (n->child) + buf_appendmdoc(buf, n->child, f); + + if (MDOC_TEXT == n->type && f) { + f = 0; + buf_appendb(buf, n->string, + strlen(n->string) + 1); + } else if (MDOC_TEXT == n->type) + buf_append(buf, n->string); + + } +} + +static void +hash_reset(DB **db) +{ + DB *hash; + + if (NULL != (hash = *db)) + (*hash->close)(hash); + + *db = dbopen(NULL, O_CREAT|O_RDWR, 0644, DB_HASH, NULL); + if (NULL == *db) { + perror("hash"); + exit((int)MANDOCLEVEL_SYSERR); + } +} + +/* ARGSUSED */ +static int +pmdoc_head(MDOC_ARGS) +{ + + return(MDOC_HEAD == n->type); +} + +/* ARGSUSED */ +static int +pmdoc_body(MDOC_ARGS) +{ + + return(MDOC_BODY == n->type); +} + +/* ARGSUSED */ +static int +pmdoc_Fd(MDOC_ARGS) +{ + const char *start, *end; + size_t sz; + + if (SEC_SYNOPSIS != n->sec) + return(0); + if (NULL == (n = n->child) || MDOC_TEXT != n->type) + return(0); + + /* + * Only consider those `Fd' macro fields that begin with an + * "inclusion" token (versus, e.g., #define). + */ + if (strcmp("#include", n->string)) + return(0); + + if (NULL == (n = n->next) || MDOC_TEXT != n->type) + return(0); + + /* + * Strip away the enclosing angle brackets and make sure we're + * not zero-length. + */ + + start = n->string; + if ('<' == *start || '"' == *start) + start++; + + if (0 == (sz = strlen(start))) + return(0); + + end = &start[(int)sz - 1]; + if ('>' == *end || '"' == *end) + end--; + + assert(end >= start); + + buf_appendb(buf, start, (size_t)(end - start + 1)); + buf_appendb(buf, "", 1); + return(1); +} + +/* ARGSUSED */ +static int +pmdoc_In(MDOC_ARGS) +{ + + if (NULL == n->child || MDOC_TEXT != n->child->type) + return(0); + + buf_append(buf, n->child->string); + return(1); +} + +/* ARGSUSED */ +static int +pmdoc_Fn(MDOC_ARGS) +{ + struct mdoc_node *nn; + const char *cp; + + nn = n->child; + + if (NULL == nn || MDOC_TEXT != nn->type) + return(0); + + /* .Fn "struct type *name" "char *arg" */ + + cp = strrchr(nn->string, ' '); + if (NULL == cp) + cp = nn->string; + + /* Strip away pointer symbol. */ + + while ('*' == *cp) + cp++; + + /* Store the function name. */ + + buf_append(buf, cp); + hash_put(hash, buf, TYPE_Fn); + + /* Store the function type. */ + + if (nn->string < cp) { + buf->len = 0; + buf_appendb(buf, nn->string, cp - nn->string); + buf_appendb(buf, "", 1); + hash_put(hash, buf, TYPE_Ft); + } + + /* Store the arguments. */ + + for (nn = nn->next; nn; nn = nn->next) { + if (MDOC_TEXT != nn->type) + continue; + buf->len = 0; + buf_append(buf, nn->string); + hash_put(hash, buf, TYPE_Fa); + } + + return(0); +} + +/* ARGSUSED */ +static int +pmdoc_St(MDOC_ARGS) +{ + + if (NULL == n->child || MDOC_TEXT != n->child->type) + return(0); + + buf_append(buf, n->child->string); + return(1); +} + +/* ARGSUSED */ +static int +pmdoc_Xr(MDOC_ARGS) +{ + + if (NULL == (n = n->child)) + return(0); + + buf_appendb(buf, n->string, strlen(n->string)); + + if (NULL != (n = n->next)) { + buf_appendb(buf, ".", 1); + buf_appendb(buf, n->string, strlen(n->string) + 1); + } else + buf_appendb(buf, ".", 2); + + return(1); +} + +/* ARGSUSED */ +static int +pmdoc_Nd(MDOC_ARGS) +{ + + if (MDOC_BODY != n->type) + return(0); + + buf_appendmdoc(dbuf, n->child, 1); + return(1); +} + +/* ARGSUSED */ +static int +pmdoc_Nm(MDOC_ARGS) +{ + + if (SEC_NAME == n->sec) + return(1); + else if (SEC_SYNOPSIS != n->sec || MDOC_HEAD != n->type) + return(0); + + if (NULL == n->child) + buf_append(buf, m->name); + + return(1); +} + +/* ARGSUSED */ +static int +pmdoc_Sh(MDOC_ARGS) +{ + + return(SEC_CUSTOM == n->sec && MDOC_HEAD == n->type); +} + +static void +hash_put(DB *db, const struct buf *buf, uint64_t mask) +{ + uint64_t oldmask; + DBT key, val; + int rc; + + if (buf->len < 2) + return; + + key.data = buf->cp; + key.size = buf->len; + + if ((rc = (*db->get)(db, &key, &val, 0)) < 0) { + perror("hash"); + exit((int)MANDOCLEVEL_SYSERR); + } else if (0 == rc) { + assert(sizeof(uint64_t) == val.size); + memcpy(&oldmask, val.data, val.size); + mask |= oldmask; + } + + val.data = &mask; + val.size = sizeof(uint64_t); + + if ((rc = (*db->put)(db, &key, &val, 0)) < 0) { + perror("hash"); + exit((int)MANDOCLEVEL_SYSERR); + } +} + +static void +dbt_put(DB *db, const char *dbn, DBT *key, DBT *val) +{ + + assert(key->size); + assert(val->size); + + if (0 == (*db->put)(db, key, val, 0)) + return; + + perror(dbn); + exit((int)MANDOCLEVEL_SYSERR); + /* NOTREACHED */ +} + +/* + * Call out to per-macro handlers after clearing the persistent database + * key. If the macro sets the database key, flush it to the database. + */ +static void +pmdoc_node(MDOC_ARGS) +{ + + if (NULL == n) + return; + + switch (n->type) { + case (MDOC_HEAD): + /* FALLTHROUGH */ + case (MDOC_BODY): + /* FALLTHROUGH */ + case (MDOC_TAIL): + /* FALLTHROUGH */ + case (MDOC_BLOCK): + /* FALLTHROUGH */ + case (MDOC_ELEM): + buf->len = 0; + + /* + * Both NULL handlers and handlers returning true + * request using the data. Only skip the element + * when the handler returns false. + */ + + if (NULL != mdocs[n->tok].fp && + 0 == (*mdocs[n->tok].fp)(hash, buf, dbuf, n, m)) + break; + + /* + * For many macros, use the text from all children. + * Set zero flags for macros not needing this. + * In that case, the handler must fill the buffer. + */ + + if (MDOCF_CHILD & mdocs[n->tok].flags) + buf_appendmdoc(buf, n->child, 0); + + /* + * Cover the most common case: + * Automatically stage one string per element. + * Set a zero mask for macros not needing this. + * Additional staging can be done in the handler. + */ + + if (mdocs[n->tok].mask) + hash_put(hash, buf, mdocs[n->tok].mask); + break; + default: + break; + } + + pmdoc_node(hash, buf, dbuf, n->child, m); + pmdoc_node(hash, buf, dbuf, n->next, m); +} + +static int +pman_node(MAN_ARGS) +{ + const struct man_node *head, *body; + char *start, *sv, *title; + size_t sz, titlesz; + + if (NULL == n) + return(0); + + /* + * We're only searching for one thing: the first text child in + * the BODY of a NAME section. Since we don't keep track of + * sections in -man, run some hoops to find out whether we're in + * the correct section or not. + */ + + if (MAN_BODY == n->type && MAN_SH == n->tok) { + body = n; + assert(body->parent); + if (NULL != (head = body->parent->head) && + 1 == head->nchild && + NULL != (head = (head->child)) && + MAN_TEXT == head->type && + 0 == strcmp(head->string, "NAME") && + NULL != (body = body->child) && + MAN_TEXT == body->type) { + + title = NULL; + titlesz = 0; + /* + * Suck the entire NAME section into memory. + * Yes, we might run away. + * But too many manuals have big, spread-out + * NAME sections over many lines. + */ + for ( ; NULL != body; body = body->next) { + if (MAN_TEXT != body->type) + break; + if (0 == (sz = strlen(body->string))) + continue; + title = mandoc_realloc + (title, titlesz + sz + 1); + memcpy(title + titlesz, body->string, sz); + titlesz += sz + 1; + title[(int)titlesz - 1] = ' '; + } + if (NULL == title) + return(0); + + title = mandoc_realloc(title, titlesz + 1); + title[(int)titlesz] = '\0'; + + /* Skip leading space. */ + + sv = title; + while (isspace((unsigned char)*sv)) + sv++; + + if (0 == (sz = strlen(sv))) { + free(title); + return(0); + } + + /* Erase trailing space. */ + + start = &sv[sz - 1]; + while (start > sv && isspace((unsigned char)*start)) + *start-- = '\0'; + + if (start == sv) { + free(title); + return(0); + } + + start = sv; + + /* + * Go through a special heuristic dance here. + * This is why -man manuals are great! + * (I'm being sarcastic: my eyes are bleeding.) + * Conventionally, one or more manual names are + * comma-specified prior to a whitespace, then a + * dash, then a description. Try to puzzle out + * the name parts here. + */ + + for ( ;; ) { + sz = strcspn(start, " ,"); + if ('\0' == start[(int)sz]) + break; + + buf->len = 0; + buf_appendb(buf, start, sz); + buf_appendb(buf, "", 1); + + hash_put(hash, buf, TYPE_Nm); + + if (' ' == start[(int)sz]) { + start += (int)sz + 1; + break; + } + + assert(',' == start[(int)sz]); + start += (int)sz + 1; + while (' ' == *start) + start++; + } + + buf->len = 0; + + if (sv == start) { + buf_append(buf, start); + free(title); + return(1); + } + + while (isspace((unsigned char)*start)) + start++; + + if (0 == strncmp(start, "-", 1)) + start += 1; + else if (0 == strncmp(start, "\\-\\-", 4)) + start += 4; + else if (0 == strncmp(start, "\\-", 2)) + start += 2; + else if (0 == strncmp(start, "\\(en", 4)) + start += 4; + else if (0 == strncmp(start, "\\(em", 4)) + start += 4; + + while (' ' == *start) + start++; + + sz = strlen(start) + 1; + buf_appendb(dbuf, start, sz); + buf_appendb(buf, start, sz); + + hash_put(hash, buf, TYPE_Nd); + free(title); + } + } + + for (n = n->child; n; n = n->next) + if (pman_node(hash, buf, dbuf, n)) + return(1); + + return(0); +} + +/* + * Parse a formatted manual page. + * By necessity, this involves rather crude guesswork. + */ +static void +pformatted(DB *hash, struct buf *buf, struct buf *dbuf, + const struct of *of, const char *basedir) +{ + FILE *stream; + char *line, *p, *title; + size_t len, plen, titlesz; + + if (NULL == (stream = fopen(of->fname, "r"))) { + WARNING(of->fname, basedir, "%s", strerror(errno)); + return; + } + + /* + * Always use the title derived from the filename up front, + * do not even try to find it in the file. This also makes + * sure we don't end up with an orphan index record, even if + * the file content turns out to be completely unintelligible. + */ + + buf->len = 0; + buf_append(buf, of->title); + hash_put(hash, buf, TYPE_Nm); + + /* Skip to first blank line. */ + + while (NULL != (line = fgetln(stream, &len))) + if ('\n' == *line) + break; + + /* + * Assume the first line that is not indented + * is the first section header. Skip to it. + */ + + while (NULL != (line = fgetln(stream, &len))) + if ('\n' != *line && ' ' != *line) + break; + + /* + * Read up until the next section into a buffer. + * Strip the leading and trailing newline from each read line, + * appending a trailing space. + * Ignore empty (whitespace-only) lines. + */ + + titlesz = 0; + title = NULL; + + while (NULL != (line = fgetln(stream, &len))) { + if (' ' != *line || '\n' != line[(int)len - 1]) + break; + while (len > 0 && isspace((unsigned char)*line)) { + line++; + len--; + } + if (1 == len) + continue; + title = mandoc_realloc(title, titlesz + len); + memcpy(title + titlesz, line, len); + titlesz += len; + title[(int)titlesz - 1] = ' '; + } + + /* + * If no page content can be found, or the input line + * is already the next section header, or there is no + * trailing newline, reuse the page title as the page + * description. + */ + + if (NULL == title || '\0' == *title) { + WARNING(of->fname, basedir, + "Cannot find NAME section"); + buf_appendb(dbuf, buf->cp, buf->size); + hash_put(hash, buf, TYPE_Nd); + fclose(stream); + free(title); + return; + } + + title = mandoc_realloc(title, titlesz + 1); + title[(int)titlesz] = '\0'; + + /* + * Skip to the first dash. + * Use the remaining line as the description (no more than 70 + * bytes). + */ + + if (NULL != (p = strstr(title, "- "))) { + for (p += 2; ' ' == *p || '\b' == *p; p++) + /* Skip to next word. */ ; + } else { + WARNING(of->fname, basedir, + "No dash in title line"); + p = title; + } + + plen = strlen(p); + + /* Strip backspace-encoding from line. */ + + while (NULL != (line = memchr(p, '\b', plen))) { + len = line - p; + if (0 == len) { + memmove(line, line + 1, plen--); + continue; + } + memmove(line - 1, line + 1, plen - len); + plen -= 2; + } + + buf_appendb(dbuf, p, plen + 1); + buf->len = 0; + buf_appendb(buf, p, plen + 1); + hash_put(hash, buf, TYPE_Nd); + fclose(stream); + free(title); +} + +static void +ofile_argbuild(int argc, char *argv[], + struct of **of, const char *basedir) +{ + char buf[MAXPATHLEN]; + const char *sec, *arch, *title; + char *p; + int i, src_form; + struct of *nof; + + for (i = 0; i < argc; i++) { + + /* + * Try to infer the manual section, architecture and + * page title from the path, assuming it looks like + * man*[/<arch>]/<title>.<section> or + * cat<section>[/<arch>]/<title>.0 + */ + + if (strlcpy(buf, argv[i], sizeof(buf)) >= sizeof(buf)) { + fprintf(stderr, "%s: Path too long\n", argv[i]); + continue; + } + sec = arch = title = ""; + src_form = 0; + p = strrchr(buf, '\0'); + while (p-- > buf) { + if ('\0' == *sec && '.' == *p) { + sec = p + 1; + *p = '\0'; + if ('0' == *sec) + src_form |= MANDOC_FORM; + else if ('1' <= *sec && '9' >= *sec) + src_form |= MANDOC_SRC; + continue; + } + if ('/' != *p) + continue; + if ('\0' == *title) { + title = p + 1; + *p = '\0'; + continue; + } + if (0 == strncmp("man", p + 1, 3)) + src_form |= MANDOC_SRC; + else if (0 == strncmp("cat", p + 1, 3)) + src_form |= MANDOC_FORM; + else + arch = p + 1; + break; + } + if ('\0' == *title) { + WARNING(argv[i], basedir, + "Cannot deduce title from filename"); + title = buf; + } + + /* + * Build the file structure. + */ + + nof = mandoc_calloc(1, sizeof(struct of)); + nof->fname = mandoc_strdup(argv[i]); + nof->sec = mandoc_strdup(sec); + nof->arch = mandoc_strdup(arch); + nof->title = mandoc_strdup(title); + nof->src_form = src_form; + + /* + * Add the structure to the list. + */ + + if (NULL == *of) { + *of = nof; + (*of)->first = nof; + } else { + nof->first = (*of)->first; + (*of)->next = nof; + *of = nof; + } + } +} + +/* + * Recursively build up a list of files to parse. + * We use this instead of ftw() and so on because I don't want global + * variables hanging around. + * This ignores the mandocdb.db and mandocdb.index files, but assumes that + * everything else is a manual. + * Pass in a pointer to a NULL structure for the first invocation. + */ +static void +ofile_dirbuild(const char *dir, const char* psec, const char *parch, + int p_src_form, struct of **of, char *basedir) +{ + char buf[MAXPATHLEN]; + size_t sz; + DIR *d; + const char *fn, *sec, *arch; + char *p, *q, *suffix; + struct of *nof; + struct dirent *dp; + int src_form; + + if (NULL == (d = opendir(dir))) { + WARNING("", dir, "%s", strerror(errno)); + return; + } + + while (NULL != (dp = readdir(d))) { + fn = dp->d_name; + + if ('.' == *fn) + continue; + + src_form = p_src_form; + + if (DT_DIR == dp->d_type) { + sec = psec; + arch = parch; + + /* + * By default, only use directories called: + * man<section>/[<arch>/] or + * cat<section>/[<arch>/] + */ + + if ('\0' == *sec) { + if(0 == strncmp("man", fn, 3)) { + src_form |= MANDOC_SRC; + sec = fn + 3; + } else if (0 == strncmp("cat", fn, 3)) { + src_form |= MANDOC_FORM; + sec = fn + 3; + } else { + WARNING(fn, basedir, "Bad section"); + if (use_all) + sec = fn; + else + continue; + } + } else if ('\0' == *arch) { + if (NULL != strchr(fn, '.')) { + WARNING(fn, basedir, "Bad architecture"); + if (0 == use_all) + continue; + } + arch = fn; + } else { + WARNING(fn, basedir, "Excessive subdirectory"); + if (0 == use_all) + continue; + } + + buf[0] = '\0'; + strlcat(buf, dir, MAXPATHLEN); + strlcat(buf, "/", MAXPATHLEN); + strlcat(basedir, "/", MAXPATHLEN); + strlcat(basedir, fn, MAXPATHLEN); + sz = strlcat(buf, fn, MAXPATHLEN); + + if (MAXPATHLEN <= sz) { + WARNING(fn, basedir, "Path too long"); + continue; + } + + ofile_dirbuild(buf, sec, arch, + src_form, of, basedir); + + p = strrchr(basedir, '/'); + *p = '\0'; + continue; + } + + if (DT_REG != dp->d_type) { + WARNING(fn, basedir, "Not a regular file"); + continue; + } + if (!strcmp(MANDOC_DB, fn) || !strcmp(MANDOC_IDX, fn)) + continue; + if ('\0' == *psec) { + WARNING(fn, basedir, "File outside section"); + if (0 == use_all) + continue; + } + + /* + * By default, skip files where the file name suffix + * does not agree with the section directory + * they are located in. + */ + + suffix = strrchr(fn, '.'); + if (NULL == suffix) { + WARNING(fn, basedir, "No filename suffix"); + if (0 == use_all) + continue; + } else if ((MANDOC_SRC & src_form && + strcmp(suffix + 1, psec)) || + (MANDOC_FORM & src_form && + strcmp(suffix + 1, "0"))) { + WARNING(fn, basedir, "Wrong filename suffix"); + if (0 == use_all) + continue; + if ('0' == suffix[1]) + src_form |= MANDOC_FORM; + else if ('1' <= suffix[1] && '9' >= suffix[1]) + src_form |= MANDOC_SRC; + } + + /* + * Skip formatted manuals if a source version is + * available. Ignore the age: it is very unlikely + * that people install newer formatted base manuals + * when they used to have source manuals before, + * and in ports, old manuals get removed on update. + */ + if (0 == use_all && MANDOC_FORM & src_form && + '\0' != *psec) { + buf[0] = '\0'; + strlcat(buf, dir, MAXPATHLEN); + p = strrchr(buf, '/'); + if ('\0' != *parch && NULL != p) + for (p--; p > buf; p--) + if ('/' == *p) + break; + if (NULL == p) + p = buf; + else + p++; + if (0 == strncmp("cat", p, 3)) + memcpy(p, "man", 3); + strlcat(buf, "/", MAXPATHLEN); + sz = strlcat(buf, fn, MAXPATHLEN); + if (sz >= MAXPATHLEN) { + WARNING(fn, basedir, "Path too long"); + continue; + } + q = strrchr(buf, '.'); + if (NULL != q && p < q++) { + *q = '\0'; + sz = strlcat(buf, psec, MAXPATHLEN); + if (sz >= MAXPATHLEN) { + WARNING(fn, basedir, "Path too long"); + continue; + } + if (0 == access(buf, R_OK)) + continue; + } + } + + buf[0] = '\0'; + assert('.' == dir[0]); + if ('/' == dir[1]) { + strlcat(buf, dir + 2, MAXPATHLEN); + strlcat(buf, "/", MAXPATHLEN); + } + sz = strlcat(buf, fn, MAXPATHLEN); + if (sz >= MAXPATHLEN) { + WARNING(fn, basedir, "Path too long"); + continue; + } + + nof = mandoc_calloc(1, sizeof(struct of)); + nof->fname = mandoc_strdup(buf); + nof->sec = mandoc_strdup(psec); + nof->arch = mandoc_strdup(parch); + nof->src_form = src_form; + + /* + * Remember the file name without the extension, + * to be used as the page title in the database. + */ + + if (NULL != suffix) + *suffix = '\0'; + nof->title = mandoc_strdup(fn); + + /* + * Add the structure to the list. + */ + + if (NULL == *of) { + *of = nof; + (*of)->first = nof; + } else { + nof->first = (*of)->first; + (*of)->next = nof; + *of = nof; + } + } + + closedir(d); +} + +static void +ofile_free(struct of *of) +{ + struct of *nof; + + if (NULL != of) + of = of->first; + + while (NULL != of) { + nof = of->next; + free(of->fname); + free(of->sec); + free(of->arch); + free(of->title); + free(of); + of = nof; + } +} diff --git a/mandocdb.h b/mandocdb.h new file mode 100644 index 000000000000..bda0536e793f --- /dev/null +++ b/mandocdb.h @@ -0,0 +1,62 @@ +/* $Id: mandocdb.h,v 1.6 2012/03/23 02:52:33 kristaps Exp $ */ +/* + * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef MANDOCDB_H +#define MANDOCDB_H + +#define MANDOC_DB "mandocdb.db" +#define MANDOC_IDX "mandocdb.index" + +#define TYPE_An 0x0000000000000001ULL +#define TYPE_Ar 0x0000000000000002ULL +#define TYPE_At 0x0000000000000004ULL +#define TYPE_Bsx 0x0000000000000008ULL +#define TYPE_Bx 0x0000000000000010ULL +#define TYPE_Cd 0x0000000000000020ULL +#define TYPE_Cm 0x0000000000000040ULL +#define TYPE_Dv 0x0000000000000080ULL +#define TYPE_Dx 0x0000000000000100ULL +#define TYPE_Em 0x0000000000000200ULL +#define TYPE_Er 0x0000000000000400ULL +#define TYPE_Ev 0x0000000000000800ULL +#define TYPE_Fa 0x0000000000001000ULL +#define TYPE_Fl 0x0000000000002000ULL +#define TYPE_Fn 0x0000000000004000ULL +#define TYPE_Ft 0x0000000000008000ULL +#define TYPE_Fx 0x0000000000010000ULL +#define TYPE_Ic 0x0000000000020000ULL +#define TYPE_In 0x0000000000040000ULL +#define TYPE_Lb 0x0000000000080000ULL +#define TYPE_Li 0x0000000000100000ULL +#define TYPE_Lk 0x0000000000200000ULL +#define TYPE_Ms 0x0000000000400000ULL +#define TYPE_Mt 0x0000000000800000ULL +#define TYPE_Nd 0x0000000001000000ULL +#define TYPE_Nm 0x0000000002000000ULL +#define TYPE_Nx 0x0000000004000000ULL +#define TYPE_Ox 0x0000000008000000ULL +#define TYPE_Pa 0x0000000010000000ULL +#define TYPE_Rs 0x0000000020000000ULL +#define TYPE_Sh 0x0000000040000000ULL +#define TYPE_Ss 0x0000000080000000ULL +#define TYPE_St 0x0000000100000000ULL +#define TYPE_Sy 0x0000000200000000ULL +#define TYPE_Tn 0x0000000400000000ULL +#define TYPE_Va 0x0000000800000000ULL +#define TYPE_Vt 0x0000001000000000ULL +#define TYPE_Xr 0x0000002000000000ULL + +#endif /*!MANDOCDB_H */ diff --git a/manpath.c b/manpath.c new file mode 100644 index 000000000000..b6d9574e3070 --- /dev/null +++ b/manpath.c @@ -0,0 +1,225 @@ +/* $Id: manpath.c,v 1.8 2011/12/24 22:37:16 kristaps Exp $ */ +/* + * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org> + * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/param.h> + +#include <assert.h> +#include <ctype.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "mandoc.h" +#include "manpath.h" + +#define MAN_CONF_FILE "/etc/man.conf" +#define MAN_CONF_KEY "_whatdb" + +static void manpath_add(struct manpaths *, const char *); +static void manpath_parseline(struct manpaths *, char *); + +void +manpath_parse(struct manpaths *dirs, const char *file, + char *defp, char *auxp) +{ +#ifdef USE_MANPATH + char cmd[(MAXPATHLEN * 3) + 20]; + FILE *stream; + char *buf; + size_t sz, bsz; + + strlcpy(cmd, "manpath", sizeof(cmd)); + if (file) { + strlcat(cmd, " -C ", sizeof(cmd)); + strlcat(cmd, file, sizeof(cmd)); + } + if (auxp) { + strlcat(cmd, " -m ", sizeof(cmd)); + strlcat(cmd, auxp, sizeof(cmd)); + } + if (defp) { + strlcat(cmd, " -M ", sizeof(cmd)); + strlcat(cmd, defp, sizeof(cmd)); + } + + /* Open manpath(1). Ignore errors. */ + + stream = popen(cmd, "r"); + if (NULL == stream) + return; + + buf = NULL; + bsz = 0; + + /* Read in as much output as we can. */ + + do { + buf = mandoc_realloc(buf, bsz + 1024); + sz = fread(buf + (int)bsz, 1, 1024, stream); + bsz += sz; + } while (sz > 0); + + if ( ! ferror(stream) && feof(stream) && + bsz && '\n' == buf[bsz - 1]) { + buf[bsz - 1] = '\0'; + manpath_parseline(dirs, buf); + } + + free(buf); + pclose(stream); +#else + char *insert; + + /* Always prepend -m. */ + manpath_parseline(dirs, auxp); + + /* If -M is given, it overrides everything else. */ + if (NULL != defp) { + manpath_parseline(dirs, defp); + return; + } + + /* MANPATH and man.conf(5) cooperate. */ + defp = getenv("MANPATH"); + if (NULL == file) + file = MAN_CONF_FILE; + + /* No MANPATH; use man.conf(5) only. */ + if (NULL == defp || '\0' == defp[0]) { + manpath_manconf(dirs, file); + return; + } + + /* Prepend man.conf(5) to MANPATH. */ + if (':' == defp[0]) { + manpath_manconf(dirs, file); + manpath_parseline(dirs, defp); + return; + } + + /* Append man.conf(5) to MANPATH. */ + if (':' == defp[(int)strlen(defp) - 1]) { + manpath_parseline(dirs, defp); + manpath_manconf(dirs, file); + return; + } + + /* Insert man.conf(5) into MANPATH. */ + insert = strstr(defp, "::"); + if (NULL != insert) { + *insert++ = '\0'; + manpath_parseline(dirs, defp); + manpath_manconf(dirs, file); + manpath_parseline(dirs, insert + 1); + return; + } + + /* MANPATH overrides man.conf(5) completely. */ + manpath_parseline(dirs, defp); +#endif +} + +/* + * Parse a FULL pathname from a colon-separated list of arrays. + */ +static void +manpath_parseline(struct manpaths *dirs, char *path) +{ + char *dir; + + if (NULL == path) + return; + + for (dir = strtok(path, ":"); dir; dir = strtok(NULL, ":")) + manpath_add(dirs, dir); +} + +/* + * Add a directory to the array, ignoring bad directories. + * Grow the array one-by-one for simplicity's sake. + */ +static void +manpath_add(struct manpaths *dirs, const char *dir) +{ + char buf[PATH_MAX]; + char *cp; + int i; + + if (NULL == (cp = realpath(dir, buf))) + return; + + for (i = 0; i < dirs->sz; i++) + if (0 == strcmp(dirs->paths[i], dir)) + return; + + dirs->paths = mandoc_realloc + (dirs->paths, + ((size_t)dirs->sz + 1) * sizeof(char *)); + + dirs->paths[dirs->sz++] = mandoc_strdup(cp); +} + +void +manpath_free(struct manpaths *p) +{ + int i; + + for (i = 0; i < p->sz; i++) + free(p->paths[i]); + + free(p->paths); +} + +void +manpath_manconf(struct manpaths *dirs, const char *file) +{ + FILE *stream; + char *p, *q; + size_t len, keysz; + + keysz = strlen(MAN_CONF_KEY); + assert(keysz > 0); + + if (NULL == (stream = fopen(file, "r"))) + return; + + while (NULL != (p = fgetln(stream, &len))) { + if (0 == len || '\n' != p[--len]) + break; + p[len] = '\0'; + while (isspace((unsigned char)*p)) + p++; + if (strncmp(MAN_CONF_KEY, p, keysz)) + continue; + p += keysz; + while (isspace(*p)) + p++; + if ('\0' == *p) + continue; + if (NULL == (q = strrchr(p, '/'))) + continue; + *q = '\0'; + manpath_add(dirs, p); + } + + fclose(stream); +} diff --git a/manpath.h b/manpath.h new file mode 100644 index 000000000000..167ee01021ea --- /dev/null +++ b/manpath.h @@ -0,0 +1,38 @@ +/* $Id: manpath.h,v 1.5 2011/12/13 20:56:46 kristaps Exp $ */ +/* + * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org> + * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef MANPATH_H +#define MANPATH_H + +/* + * Unsorted list of unique, absolute paths to be searched for manual + * databases. + */ +struct manpaths { + int sz; + char **paths; +}; + +__BEGIN_DECLS + +void manpath_manconf(struct manpaths *, const char *); +void manpath_parse(struct manpaths *, const char *, char *, char *); +void manpath_free(struct manpaths *); + +__END_DECLS + +#endif /*!MANPATH_H*/ diff --git a/mdoc.7 b/mdoc.7 new file mode 100644 index 000000000000..44d927b56b82 --- /dev/null +++ b/mdoc.7 @@ -0,0 +1,3172 @@ +.\" $Id: mdoc.7,v 1.214 2012/01/03 10:18:05 kristaps Exp $ +.\" +.\" Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> +.\" Copyright (c) 2010, 2011 Ingo Schwarze <schwarze@openbsd.org> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: January 3 2012 $ +.Dt MDOC 7 +.Os +.Sh NAME +.Nm mdoc +.Nd semantic markup language for formatting manual pages +.Sh DESCRIPTION +The +.Nm mdoc +language supports authoring of manual pages for the +.Xr man 1 +utility by allowing semantic annotations of words, phrases, +page sections and complete manual pages. +Such annotations are used by formatting tools to achieve a uniform +presentation across all manuals written in +.Nm , +and to support hyperlinking if supported by the output medium. +.Pp +This reference document describes the structure of manual pages +and the syntax and usage of the +.Nm +language. +The reference implementation of a parsing and formatting tool is +.Xr mandoc 1 ; +the +.Sx COMPATIBILITY +section describes compatibility with other implementations. +.Pp +In an +.Nm +document, lines beginning with the control character +.Sq \&. +are called +.Dq macro lines . +The first word is the macro name. +It consists of two or three letters. +Most macro names begin with a capital letter. +For a list of available macros, see +.Sx MACRO OVERVIEW . +The words following the macro name are arguments to the macro, optionally +including the names of other, callable macros; see +.Sx MACRO SYNTAX +for details. +.Pp +Lines not beginning with the control character are called +.Dq text lines . +They provide free-form text to be printed; the formatting of the text +depends on the respective processing context: +.Bd -literal -offset indent +\&.Sh Macro lines change control state. +Text lines are interpreted within the current state. +.Ed +.Pp +Many aspects of the basic syntax of the +.Nm +language are based on the +.Xr roff 7 +language; see the +.Em LANGUAGE SYNTAX +and +.Em MACRO SYNTAX +sections in the +.Xr roff 7 +manual for details, in particular regarding +comments, escape sequences, whitespace, and quoting. +However, using +.Xr roff 7 +requests in +.Nm +documents is discouraged; +.Xr mandoc 1 +supports some of them merely for backward compatibility. +.Sh MANUAL STRUCTURE +A well-formed +.Nm +document consists of a document prologue followed by one or more +sections. +.Pp +The prologue, which consists of the +.Sx \&Dd , +.Sx \&Dt , +and +.Sx \&Os +macros in that order, is required for every document. +.Pp +The first section (sections are denoted by +.Sx \&Sh ) +must be the NAME section, consisting of at least one +.Sx \&Nm +followed by +.Sx \&Nd . +.Pp +Following that, convention dictates specifying at least the +.Em SYNOPSIS +and +.Em DESCRIPTION +sections, although this varies between manual sections. +.Pp +The following is a well-formed skeleton +.Nm +file for a utility +.Qq progname : +.Bd -literal -offset indent +\&.Dd $\&Mdocdate$ +\&.Dt PROGNAME section +\&.Os +\&.Sh NAME +\&.Nm progname +\&.Nd one line about what it does +\&.\e\(dq .Sh LIBRARY +\&.\e\(dq For sections 2, 3, & 9 only. +\&.\e\(dq Not used in OpenBSD. +\&.Sh SYNOPSIS +\&.Nm progname +\&.Op Fl options +\&.Ar +\&.Sh DESCRIPTION +The +\&.Nm +utility processes files ... +\&.\e\(dq .Sh IMPLEMENTATION NOTES +\&.\e\(dq Not used in OpenBSD. +\&.\e\(dq .Sh RETURN VALUES +\&.\e\(dq For sections 2, 3, & 9 only. +\&.\e\(dq .Sh ENVIRONMENT +\&.\e\(dq For sections 1, 6, 7, & 8 only. +\&.\e\(dq .Sh FILES +\&.\e\(dq .Sh EXIT STATUS +\&.\e\(dq For sections 1, 6, & 8 only. +\&.\e\(dq .Sh EXAMPLES +\&.\e\(dq .Sh DIAGNOSTICS +\&.\e\(dq For sections 1, 4, 6, 7, & 8 only. +\&.\e\(dq .Sh ERRORS +\&.\e\(dq For sections 2, 3, & 9 only. +\&.\e\(dq .Sh SEE ALSO +\&.\e\(dq .Xr foobar 1 +\&.\e\(dq .Sh STANDARDS +\&.\e\(dq .Sh HISTORY +\&.\e\(dq .Sh AUTHORS +\&.\e\(dq .Sh CAVEATS +\&.\e\(dq .Sh BUGS +\&.\e\(dq .Sh SECURITY CONSIDERATIONS +\&.\e\(dq Not used in OpenBSD. +.Ed +.Pp +The sections in an +.Nm +document are conventionally ordered as they appear above. +Sections should be composed as follows: +.Bl -ohang -offset Ds +.It Em NAME +The name(s) and a one line description of the documented material. +The syntax for this as follows: +.Bd -literal -offset indent +\&.Nm name0 , +\&.Nm name1 , +\&.Nm name2 +\&.Nd a one line description +.Ed +.Pp +Multiple +.Sq \&Nm +names should be separated by commas. +.Pp +The +.Sx \&Nm +macro(s) must precede the +.Sx \&Nd +macro. +.Pp +See +.Sx \&Nm +and +.Sx \&Nd . +.It Em LIBRARY +The name of the library containing the documented material, which is +assumed to be a function in a section 2, 3, or 9 manual. +The syntax for this is as follows: +.Bd -literal -offset indent +\&.Lb libarm +.Ed +.Pp +See +.Sx \&Lb . +.It Em SYNOPSIS +Documents the utility invocation syntax, function call syntax, or device +configuration. +.Pp +For the first, utilities (sections 1, 6, and 8), this is +generally structured as follows: +.Bd -literal -offset indent +\&.Nm bar +\&.Op Fl v +\&.Op Fl o Ar file +\&.Op Ar +\&.Nm foo +\&.Op Fl v +\&.Op Fl o Ar file +\&.Op Ar +.Ed +.Pp +Commands should be ordered alphabetically. +.Pp +For the second, function calls (sections 2, 3, 9): +.Bd -literal -offset indent +\&.In header.h +\&.Vt extern const char *global; +\&.Ft "char *" +\&.Fn foo "const char *src" +\&.Ft "char *" +\&.Fn bar "const char *src" +.Ed +.Pp +Ordering of +.Sx \&In , +.Sx \&Vt , +.Sx \&Fn , +and +.Sx \&Fo +macros should follow C header-file conventions. +.Pp +And for the third, configurations (section 4): +.Bd -literal -offset indent +\&.Cd \(dqit* at isa? port 0x2e\(dq +\&.Cd \(dqit* at isa? port 0x4e\(dq +.Ed +.Pp +Manuals not in these sections generally don't need a +.Em SYNOPSIS . +.Pp +Some macros are displayed differently in the +.Em SYNOPSIS +section, particularly +.Sx \&Nm , +.Sx \&Cd , +.Sx \&Fd , +.Sx \&Fn , +.Sx \&Fo , +.Sx \&In , +.Sx \&Vt , +and +.Sx \&Ft . +All of these macros are output on their own line. +If two such dissimilar macros are pairwise invoked (except for +.Sx \&Ft +before +.Sx \&Fo +or +.Sx \&Fn ) , +they are separated by a vertical space, unless in the case of +.Sx \&Fo , +.Sx \&Fn , +and +.Sx \&Ft , +which are always separated by vertical space. +.Pp +When text and macros following an +.Sx \&Nm +macro starting an input line span multiple output lines, +all output lines but the first will be indented to align +with the text immediately following the +.Sx \&Nm +macro, up to the next +.Sx \&Nm , +.Sx \&Sh , +or +.Sx \&Ss +macro or the end of an enclosing block, whichever comes first. +.It Em DESCRIPTION +This begins with an expansion of the brief, one line description in +.Em NAME : +.Bd -literal -offset indent +The +\&.Nm +utility does this, that, and the other. +.Ed +.Pp +It usually follows with a breakdown of the options (if documenting a +command), such as: +.Bd -literal -offset indent +The arguments are as follows: +\&.Bl \-tag \-width Ds +\&.It Fl v +Print verbose information. +\&.El +.Ed +.Pp +Manuals not documenting a command won't include the above fragment. +.Pp +Since the +.Em DESCRIPTION +section usually contains most of the text of a manual, longer manuals +often use the +.Sx \&Ss +macro to form subsections. +In very long manuals, the +.Em DESCRIPTION +may be split into multiple sections, each started by an +.Sx \&Sh +macro followed by a non-standard section name, and each having +several subsections, like in the present +.Nm +manual. +.It Em IMPLEMENTATION NOTES +Implementation-specific notes should be kept here. +This is useful when implementing standard functions that may have side +effects or notable algorithmic implications. +.It Em RETURN VALUES +This section documents the +return values of functions in sections 2, 3, and 9. +.Pp +See +.Sx \&Rv . +.It Em ENVIRONMENT +Lists the environment variables used by the utility, +and explains the syntax and semantics of their values. +The +.Xr environ 7 +manual provides examples of typical content and formatting. +.Pp +See +.Sx \&Ev . +.It Em FILES +Documents files used. +It's helpful to document both the file name and a short description of how +the file is used (created, modified, etc.). +.Pp +See +.Sx \&Pa . +.It Em EXIT STATUS +This section documents the +command exit status for section 1, 6, and 8 utilities. +Historically, this information was described in +.Em DIAGNOSTICS , +a practise that is now discouraged. +.Pp +See +.Sx \&Ex . +.It Em EXAMPLES +Example usages. +This often contains snippets of well-formed, well-tested invocations. +Make sure that examples work properly! +.It Em DIAGNOSTICS +Documents error conditions. +This is most useful in section 4 manuals. +Historically, this section was used in place of +.Em EXIT STATUS +for manuals in sections 1, 6, and 8; however, this practise is +discouraged. +.Pp +See +.Sx \&Bl +.Fl diag . +.It Em ERRORS +Documents error handling in sections 2, 3, and 9. +.Pp +See +.Sx \&Er . +.It Em SEE ALSO +References other manuals with related topics. +This section should exist for most manuals. +Cross-references should conventionally be ordered first by section, then +alphabetically. +.Pp +References to other documentation concerning the topic of the manual page, +for example authoritative books or journal articles, may also be +provided in this section. +.Pp +See +.Sx \&Rs +and +.Sx \&Xr . +.It Em STANDARDS +References any standards implemented or used. +If not adhering to any standards, the +.Em HISTORY +section should be used instead. +.Pp +See +.Sx \&St . +.It Em HISTORY +A brief history of the subject, including where it was first implemented, +and when it was ported to or reimplemented for the operating system at hand. +.It Em AUTHORS +Credits to the person or persons who wrote the code and/or documentation. +Authors should generally be noted by both name and email address. +.Pp +See +.Sx \&An . +.It Em CAVEATS +Common misuses and misunderstandings should be explained +in this section. +.It Em BUGS +Known bugs, limitations, and work-arounds should be described +in this section. +.It Em SECURITY CONSIDERATIONS +Documents any security precautions that operators should consider. +.El +.Sh MACRO OVERVIEW +This overview is sorted such that macros of similar purpose are listed +together, to help find the best macro for any given purpose. +Deprecated macros are not included in the overview, but can be found below +in the alphabetical +.Sx MACRO REFERENCE . +.Ss Document preamble and NAME section macros +.Bl -column "Brq, Bro, Brc" description +.It Sx \&Dd Ta document date: Cm $\&Mdocdate$ | Ar month day , year +.It Sx \&Dt Ta document title: Ar TITLE section Op Ar volume | arch +.It Sx \&Os Ta operating system version: Op Ar system Op Ar version +.It Sx \&Nm Ta document name (one argument) +.It Sx \&Nd Ta document description (one line) +.El +.Ss Sections and cross references +.Bl -column "Brq, Bro, Brc" description +.It Sx \&Sh Ta section header (one line) +.It Sx \&Ss Ta subsection header (one line) +.It Sx \&Sx Ta internal cross reference to a section or subsection +.It Sx \&Xr Ta cross reference to another manual page: Ar name section +.It Sx \&Pp , \&Lp Ta start a text paragraph (no arguments) +.El +.Ss Displays and lists +.Bl -column "Brq, Bro, Brc" description +.It Sx \&Bd , \&Ed Ta display block: +.Fl Ar type +.Op Fl offset Ar width +.Op Fl compact +.It Sx \&D1 Ta indented display (one line) +.It Sx \&Dl Ta indented literal display (one line) +.It Sx \&Bl , \&El Ta list block: +.Fl Ar type +.Op Fl width Ar val +.Op Fl offset Ar val +.Op Fl compact +.It Sx \&It Ta list item (syntax depends on Fl Ar type ) +.It Sx \&Ta Ta table cell separator in Sx \&Bl Fl column No lists +.It Sx \&Rs , \&%* , \&Re Ta bibliographic block (references) +.El +.Ss Spacing control +.Bl -column "Brq, Bro, Brc" description +.It Sx \&Pf Ta prefix, no following horizontal space (one argument) +.It Sx \&Ns Ta roman font, no preceding horizontal space (no arguments) +.It Sx \&Ap Ta apostrophe without surrounding whitespace (no arguments) +.It Sx \&Sm Ta switch horizontal spacing mode: Cm on | off +.It Sx \&Bk , \&Ek Ta keep block: Fl words +.It Sx \&br Ta force output line break in text mode (no arguments) +.It Sx \&sp Ta force vertical space: Op Ar height +.El +.Ss Semantic markup for command line utilities: +.Bl -column "Brq, Bro, Brc" description +.It Sx \&Nm Ta start a SYNOPSIS block with the name of a utility +.It Sx \&Fl Ta command line options (flags) (>=0 arguments) +.It Sx \&Cm Ta command modifier (>0 arguments) +.It Sx \&Ar Ta command arguments (>=0 arguments) +.It Sx \&Op , \&Oo , \&Oc Ta optional syntax elements (enclosure) +.It Sx \&Ic Ta internal or interactive command (>0 arguments) +.It Sx \&Ev Ta environmental variable (>0 arguments) +.It Sx \&Pa Ta file system path (>=0 arguments) +.El +.Ss Semantic markup for function libraries: +.Bl -column "Brq, Bro, Brc" description +.It Sx \&Lb Ta function library (one argument) +.It Sx \&In Ta include file (one argument) +.It Sx \&Ft Ta function type (>0 arguments) +.It Sx \&Fo , \&Fc Ta function block: Ar funcname +.It Sx \&Fn Ta function name: +.Op Ar functype +.Ar funcname +.Oo +.Op Ar argtype +.Ar argname +.Oc +.It Sx \&Fa Ta function argument (>0 arguments) +.It Sx \&Vt Ta variable type (>0 arguments) +.It Sx \&Va Ta variable name (>0 arguments) +.It Sx \&Dv Ta defined variable or preprocessor constant (>0 arguments) +.It Sx \&Er Ta error constant (>0 arguments) +.It Sx \&Ev Ta environmental variable (>0 arguments) +.El +.Ss Various semantic markup: +.Bl -column "Brq, Bro, Brc" description +.It Sx \&An Ta author name (>0 arguments) +.It Sx \&Lk Ta hyperlink: Ar uri Op Ar name +.It Sx \&Mt Ta Do mailto Dc hyperlink: Ar address +.It Sx \&Cd Ta kernel configuration declaration (>0 arguments) +.It Sx \&Ad Ta memory address (>0 arguments) +.It Sx \&Ms Ta mathematical symbol (>0 arguments) +.It Sx \&Tn Ta tradename (>0 arguments) +.El +.Ss Physical markup +.Bl -column "Brq, Bro, Brc" description +.It Sx \&Em Ta italic font or underline (emphasis) (>0 arguments) +.It Sx \&Sy Ta boldface font (symbolic) (>0 arguments) +.It Sx \&Li Ta typewriter font (literal) (>0 arguments) +.It Sx \&No Ta return to roman font (normal) (no arguments) +.It Sx \&Bf , \&Ef Ta font block: +.Op Fl Ar type | Cm \&Em | \&Li | \&Sy +.El +.Ss Physical enclosures +.Bl -column "Brq, Bro, Brc" description +.It Sx \&Dq , \&Do , \&Dc Ta enclose in typographic double quotes: Dq text +.It Sx \&Qq , \&Qo , \&Qc Ta enclose in typewriter double quotes: Qq text +.It Sx \&Sq , \&So , \&Sc Ta enclose in single quotes: Sq text +.It Sx \&Ql Ta single-quoted literal text: Ql text +.It Sx \&Pq , \&Po , \&Pc Ta enclose in parentheses: Pq text +.It Sx \&Bq , \&Bo , \&Bc Ta enclose in square brackets: Bq text +.It Sx \&Brq , \&Bro , \&Brc Ta enclose in curly braces: Brq text +.It Sx \&Aq , \&Ao , \&Ac Ta enclose in angle brackets: Aq text +.It Sx \&Eo , \&Ec Ta generic enclosure +.El +.Ss Text production +.Bl -column "Brq, Bro, Brc" description +.It Sx \&Ex Fl std Ta standard command exit values: Op Ar utility ... +.It Sx \&Rv Fl std Ta standard function return values: Op Ar function ... +.It Sx \&St Ta reference to a standards document (one argument) +.It Sx \&Ux Ta Ux +.It Sx \&At Ta At +.It Sx \&Bx Ta Bx +.It Sx \&Bsx Ta Bsx +.It Sx \&Nx Ta Nx +.It Sx \&Fx Ta Fx +.It Sx \&Ox Ta Ox +.It Sx \&Dx Ta Dx +.El +.Sh MACRO REFERENCE +This section is a canonical reference of all macros, arranged +alphabetically. +For the scoping of individual macros, see +.Sx MACRO SYNTAX . +.Ss \&%A +Author name of an +.Sx \&Rs +block. +Multiple authors should each be accorded their own +.Sx \%%A +line. +Author names should be ordered with full or abbreviated forename(s) +first, then full surname. +.Ss \&%B +Book title of an +.Sx \&Rs +block. +This macro may also be used in a non-bibliographic context when +referring to book titles. +.Ss \&%C +Publication city or location of an +.Sx \&Rs +block. +.Ss \&%D +Publication date of an +.Sx \&Rs +block. +Recommended formats of arguments are +.Ar month day , year +or just +.Ar year . +.Ss \&%I +Publisher or issuer name of an +.Sx \&Rs +block. +.Ss \&%J +Journal name of an +.Sx \&Rs +block. +.Ss \&%N +Issue number (usually for journals) of an +.Sx \&Rs +block. +.Ss \&%O +Optional information of an +.Sx \&Rs +block. +.Ss \&%P +Book or journal page number of an +.Sx \&Rs +block. +.Ss \&%Q +Institutional author (school, government, etc.) of an +.Sx \&Rs +block. +Multiple institutional authors should each be accorded their own +.Sx \&%Q +line. +.Ss \&%R +Technical report name of an +.Sx \&Rs +block. +.Ss \&%T +Article title of an +.Sx \&Rs +block. +This macro may also be used in a non-bibliographical context when +referring to article titles. +.Ss \&%U +URI of reference document. +.Ss \&%V +Volume number of an +.Sx \&Rs +block. +.Ss \&Ac +Close an +.Sx \&Ao +block. +Does not have any tail arguments. +.Ss \&Ad +Memory address. +Do not use this for postal addresses. +.Pp +Examples: +.Dl \&.Ad [0,$] +.Dl \&.Ad 0x00000000 +.Ss \&An +Author name. +Can be used both for the authors of the program, function, or driver +documented in the manual, or for the authors of the manual itself. +Requires either the name of an author or one of the following arguments: +.Pp +.Bl -tag -width "-nosplitX" -offset indent -compact +.It Fl split +Start a new output line before each subsequent invocation of +.Sx \&An . +.It Fl nosplit +The opposite of +.Fl split . +.El +.Pp +The default is +.Fl nosplit . +The effect of selecting either of the +.Fl split +modes ends at the beginning of the +.Em AUTHORS +section. +In the +.Em AUTHORS +section, the default is +.Fl nosplit +for the first author listing and +.Fl split +for all other author listings. +.Pp +Examples: +.Dl \&.An -nosplit +.Dl \&.An Kristaps Dzonsons \&Aq kristaps@bsd.lv +.Ss \&Ao +Begin a block enclosed by angle brackets. +Does not have any head arguments. +.Pp +Examples: +.Dl \&.Fl -key= \&Ns \&Ao \&Ar val \&Ac +.Pp +See also +.Sx \&Aq . +.Ss \&Ap +Inserts an apostrophe without any surrounding whitespace. +This is generally used as a grammatical device when referring to the verb +form of a function. +.Pp +Examples: +.Dl \&.Fn execve \&Ap d +.Ss \&Aq +Encloses its arguments in angle brackets. +.Pp +Examples: +.Dl \&.Fl -key= \&Ns \&Aq \&Ar val +.Pp +.Em Remarks : +this macro is often abused for rendering URIs, which should instead use +.Sx \&Lk +or +.Sx \&Mt , +or to note pre-processor +.Dq Li #include +statements, which should use +.Sx \&In . +.Pp +See also +.Sx \&Ao . +.Ss \&Ar +Command arguments. +If an argument is not provided, the string +.Dq file ...\& +is used as a default. +.Pp +Examples: +.Dl ".Fl o Ar file" +.Dl ".Ar" +.Dl ".Ar arg1 , arg2 ." +.Pp +The arguments to the +.Sx \&Ar +macro are names and placeholders for command arguments; +for fixed strings to be passed verbatim as arguments, use +.Sx \&Fl +or +.Sx \&Cm . +.Ss \&At +Formats an AT&T version. +Accepts one optional argument: +.Pp +.Bl -tag -width "v[1-7] | 32vX" -offset indent -compact +.It Cm v[1-7] | 32v +A version of +.At . +.It Cm III +.At III . +.It Cm V[.[1-4]]? +A version of +.At V . +.El +.Pp +Note that these arguments do not begin with a hyphen. +.Pp +Examples: +.Dl \&.At +.Dl \&.At III +.Dl \&.At V.1 +.Pp +See also +.Sx \&Bsx , +.Sx \&Bx , +.Sx \&Dx , +.Sx \&Fx , +.Sx \&Nx , +.Sx \&Ox , +and +.Sx \&Ux . +.Ss \&Bc +Close a +.Sx \&Bo +block. +Does not have any tail arguments. +.Ss \&Bd +Begin a display block. +Its syntax is as follows: +.Bd -ragged -offset indent +.Pf \. Sx \&Bd +.Fl Ns Ar type +.Op Fl offset Ar width +.Op Fl compact +.Ed +.Pp +Display blocks are used to select a different indentation and +justification than the one used by the surrounding text. +They may contain both macro lines and text lines. +By default, a display block is preceded by a vertical space. +.Pp +The +.Ar type +must be one of the following: +.Bl -tag -width 13n -offset indent +.It Fl centered +Produce one output line from each input line, and centre-justify each line. +Using this display type is not recommended; many +.Nm +implementations render it poorly. +.It Fl filled +Change the positions of line breaks to fill each line, and left- and +right-justify the resulting block. +.It Fl literal +Produce one output line from each input line, +and do not justify the block at all. +Preserve white space as it appears in the input. +Always use a constant-width font. +Use this for displaying source code. +.It Fl ragged +Change the positions of line breaks to fill each line, and left-justify +the resulting block. +.It Fl unfilled +The same as +.Fl literal , +but using the same font as for normal text, which is a variable width font +if supported by the output device. +.El +.Pp +The +.Ar type +must be provided first. +Additional arguments may follow: +.Bl -tag -width 13n -offset indent +.It Fl offset Ar width +Indent the display by the +.Ar width , +which may be one of the following: +.Bl -item +.It +One of the pre-defined strings +.Cm indent , +the width of a standard indentation (six constant width characters); +.Cm indent-two , +twice +.Cm indent ; +.Cm left , +which has no effect; +.Cm right , +which justifies to the right margin; or +.Cm center , +which aligns around an imagined centre axis. +.It +A macro invocation, which selects a predefined width +associated with that macro. +The most popular is the imaginary macro +.Ar \&Ds , +which resolves to +.Sy 6n . +.It +A width using the syntax described in +.Sx Scaling Widths . +.It +An arbitrary string, which indents by the length of this string. +.El +.Pp +When the argument is missing, +.Fl offset +is ignored. +.It Fl compact +Do not assert vertical space before the display. +.El +.Pp +Examples: +.Bd -literal -offset indent +\&.Bd \-literal \-offset indent \-compact + Hello world. +\&.Ed +.Ed +.Pp +See also +.Sx \&D1 +and +.Sx \&Dl . +.Ss \&Bf +Change the font mode for a scoped block of text. +Its syntax is as follows: +.Bd -ragged -offset indent +.Pf \. Sx \&Bf +.Oo +.Fl emphasis | literal | symbolic | +.Cm \&Em | \&Li | \&Sy +.Oc +.Ed +.Pp +The +.Fl emphasis +and +.Cm \&Em +argument are equivalent, as are +.Fl symbolic +and +.Cm \&Sy , +and +.Fl literal +and +.Cm \&Li . +Without an argument, this macro does nothing. +The font mode continues until broken by a new font mode in a nested +scope or +.Sx \&Ef +is encountered. +.Pp +See also +.Sx \&Li , +.Sx \&Ef , +.Sx \&Em , +and +.Sx \&Sy . +.Ss \&Bk +For each macro, keep its output together on the same output line, +until the end of the macro or the end of the input line is reached, +whichever comes first. +Line breaks in text lines are unaffected. +The syntax is as follows: +.Pp +.D1 Pf \. Sx \&Bk Fl words +.Pp +The +.Fl words +argument is required; additional arguments are ignored. +.Pp +The following example will not break within each +.Sx \&Op +macro line: +.Bd -literal -offset indent +\&.Bk \-words +\&.Op Fl f Ar flags +\&.Op Fl o Ar output +\&.Ek +.Ed +.Pp +Be careful in using over-long lines within a keep block! +Doing so will clobber the right margin. +.Ss \&Bl +Begin a list. +Lists consist of items specified using the +.Sx \&It +macro, containing a head or a body or both. +The list syntax is as follows: +.Bd -ragged -offset indent +.Pf \. Sx \&Bl +.Fl Ns Ar type +.Op Fl width Ar val +.Op Fl offset Ar val +.Op Fl compact +.Op HEAD ... +.Ed +.Pp +The list +.Ar type +is mandatory and must be specified first. +The +.Fl width +and +.Fl offset +arguments accept +.Sx Scaling Widths +or use the length of the given string. +The +.Fl offset +is a global indentation for the whole list, affecting both item heads +and bodies. +For those list types supporting it, the +.Fl width +argument requests an additional indentation of item bodies, +to be added to the +.Fl offset . +Unless the +.Fl compact +argument is specified, list entries are separated by vertical space. +.Pp +A list must specify one of the following list types: +.Bl -tag -width 12n -offset indent +.It Fl bullet +No item heads can be specified, but a bullet will be printed at the head +of each item. +Item bodies start on the same output line as the bullet +and are indented according to the +.Fl width +argument. +.It Fl column +A columnated list. +The +.Fl width +argument has no effect; instead, each argument specifies the width +of one column, using either the +.Sx Scaling Widths +syntax or the string length of the argument. +If the first line of the body of a +.Fl column +list is not an +.Sx \&It +macro line, +.Sx \&It +contexts spanning one input line each are implied until an +.Sx \&It +macro line is encountered, at which point items start being interpreted as +described in the +.Sx \&It +documentation. +.It Fl dash +Like +.Fl bullet , +except that dashes are used in place of bullets. +.It Fl diag +Like +.Fl inset , +except that item heads are not parsed for macro invocations. +Most often used in the +.Em DIAGNOSTICS +section with error constants in the item heads. +.It Fl enum +A numbered list. +No item heads can be specified. +Formatted like +.Fl bullet , +except that cardinal numbers are used in place of bullets, +starting at 1. +.It Fl hang +Like +.Fl tag , +except that the first lines of item bodies are not indented, but follow +the item heads like in +.Fl inset +lists. +.It Fl hyphen +Synonym for +.Fl dash . +.It Fl inset +Item bodies follow items heads on the same line, using normal inter-word +spacing. +Bodies are not indented, and the +.Fl width +argument is ignored. +.It Fl item +No item heads can be specified, and none are printed. +Bodies are not indented, and the +.Fl width +argument is ignored. +.It Fl ohang +Item bodies start on the line following item heads and are not indented. +The +.Fl width +argument is ignored. +.It Fl tag +Item bodies are indented according to the +.Fl width +argument. +When an item head fits inside the indentation, the item body follows +this head on the same output line. +Otherwise, the body starts on the output line following the head. +.El +.Pp +Lists may be nested within lists and displays. +Nesting of +.Fl column +and +.Fl enum +lists may not be portable. +.Pp +See also +.Sx \&El +and +.Sx \&It . +.Ss \&Bo +Begin a block enclosed by square brackets. +Does not have any head arguments. +.Pp +Examples: +.Bd -literal -offset indent -compact +\&.Bo 1 , +\&.Dv BUFSIZ \&Bc +.Ed +.Pp +See also +.Sx \&Bq . +.Ss \&Bq +Encloses its arguments in square brackets. +.Pp +Examples: +.Dl \&.Bq 1 , \&Dv BUFSIZ +.Pp +.Em Remarks : +this macro is sometimes abused to emulate optional arguments for +commands; the correct macros to use for this purpose are +.Sx \&Op , +.Sx \&Oo , +and +.Sx \&Oc . +.Pp +See also +.Sx \&Bo . +.Ss \&Brc +Close a +.Sx \&Bro +block. +Does not have any tail arguments. +.Ss \&Bro +Begin a block enclosed by curly braces. +Does not have any head arguments. +.Pp +Examples: +.Bd -literal -offset indent -compact +\&.Bro 1 , ... , +\&.Va n \&Brc +.Ed +.Pp +See also +.Sx \&Brq . +.Ss \&Brq +Encloses its arguments in curly braces. +.Pp +Examples: +.Dl \&.Brq 1 , ... , \&Va n +.Pp +See also +.Sx \&Bro . +.Ss \&Bsx +Format the BSD/OS version provided as an argument, or a default value if +no argument is provided. +.Pp +Examples: +.Dl \&.Bsx 1.0 +.Dl \&.Bsx +.Pp +See also +.Sx \&At , +.Sx \&Bx , +.Sx \&Dx , +.Sx \&Fx , +.Sx \&Nx , +.Sx \&Ox , +and +.Sx \&Ux . +.Ss \&Bt +Prints +.Dq is currently in beta test. +.Ss \&Bx +Format the BSD version provided as an argument, or a default value if no +argument is provided. +.Pp +Examples: +.Dl \&.Bx 4.3 Tahoe +.Dl \&.Bx 4.4 +.Dl \&.Bx +.Pp +See also +.Sx \&At , +.Sx \&Bsx , +.Sx \&Dx , +.Sx \&Fx , +.Sx \&Nx , +.Sx \&Ox , +and +.Sx \&Ux . +.Ss \&Cd +Kernel configuration declaration. +This denotes strings accepted by +.Xr config 8 . +It is most often used in section 4 manual pages. +.Pp +Examples: +.Dl \&.Cd device le0 at scode? +.Pp +.Em Remarks : +this macro is commonly abused by using quoted literals to retain +whitespace and align consecutive +.Sx \&Cd +declarations. +This practise is discouraged. +.Ss \&Cm +Command modifiers. +Typically used for fixed strings passed as arguments, unless +.Sx \&Fl +is more appropriate. +Also useful when specifying configuration options or keys. +.Pp +Examples: +.Dl ".Nm mt Fl f Ar device Cm rewind" +.Dl ".Nm ps Fl o Cm pid , Ns Cm command" +.Dl ".Nm dd Cm if= Ns Ar file1 Cm of= Ns Ar file2" +.Dl ".Cm IdentityFile Pa ~/.ssh/id_rsa" +.Dl ".Cm LogLevel Dv DEBUG" +.Ss \&D1 +One-line indented display. +This is formatted by the default rules and is useful for simple indented +statements. +It is followed by a newline. +.Pp +Examples: +.Dl \&.D1 \&Fl abcdefgh +.Pp +See also +.Sx \&Bd +and +.Sx \&Dl . +.Ss \&Db +Switch debugging mode. +Its syntax is as follows: +.Pp +.D1 Pf \. Sx \&Db Cm on | off +.Pp +This macro is ignored by +.Xr mandoc 1 . +.Ss \&Dc +Close a +.Sx \&Do +block. +Does not have any tail arguments. +.Ss \&Dd +Document date. +This is the mandatory first macro of any +.Nm +manual. +Its syntax is as follows: +.Pp +.D1 Pf \. Sx \&Dd Ar month day , year +.Pp +The +.Ar month +is the full English month name, the +.Ar day +is an optionally zero-padded numeral, and the +.Ar year +is the full four-digit year. +.Pp +Other arguments are not portable; the +.Xr mandoc 1 +utility handles them as follows: +.Bl -dash -offset 3n -compact +.It +To have the date automatically filled in by the +.Ox +version of +.Xr cvs 1 , +the special string +.Dq $\&Mdocdate$ +can be given as an argument. +.It +A few alternative date formats are accepted as well +and converted to the standard form. +.It +If a date string cannot be parsed, it is used verbatim. +.It +If no date string is given, the current date is used. +.El +.Pp +Examples: +.Dl \&.Dd $\&Mdocdate$ +.Dl \&.Dd $\&Mdocdate: July 21 2007$ +.Dl \&.Dd July 21, 2007 +.Pp +See also +.Sx \&Dt +and +.Sx \&Os . +.Ss \&Dl +One-line intended display. +This is formatted as literal text and is useful for commands and +invocations. +It is followed by a newline. +.Pp +Examples: +.Dl \&.Dl % mandoc mdoc.7 \e(ba less +.Pp +See also +.Sx \&Bd +and +.Sx \&D1 . +.Ss \&Do +Begin a block enclosed by double quotes. +Does not have any head arguments. +.Pp +Examples: +.Bd -literal -offset indent -compact +\&.Do +April is the cruellest month +\&.Dc +\e(em T.S. Eliot +.Ed +.Pp +See also +.Sx \&Dq . +.Ss \&Dq +Encloses its arguments in +.Dq typographic +double-quotes. +.Pp +Examples: +.Bd -literal -offset indent -compact +\&.Dq April is the cruellest month +\e(em T.S. Eliot +.Ed +.Pp +See also +.Sx \&Qq , +.Sx \&Sq , +and +.Sx \&Do . +.Ss \&Dt +Document title. +This is the mandatory second macro of any +.Nm +file. +Its syntax is as follows: +.Bd -ragged -offset indent +.Pf \. Sx \&Dt +.Oo +.Ar title +.Oo +.Ar section +.Op Ar volume +.Op Ar arch +.Oc +.Oc +.Ed +.Pp +Its arguments are as follows: +.Bl -tag -width Ds -offset Ds +.It Ar title +The document's title (name), defaulting to +.Dq UNKNOWN +if unspecified. +It should be capitalised. +.It Ar section +The manual section. +This may be one of +.Ar 1 +.Pq utilities , +.Ar 2 +.Pq system calls , +.Ar 3 +.Pq libraries , +.Ar 3p +.Pq Perl libraries , +.Ar 4 +.Pq devices , +.Ar 5 +.Pq file formats , +.Ar 6 +.Pq games , +.Ar 7 +.Pq miscellaneous , +.Ar 8 +.Pq system utilities , +.Ar 9 +.Pq kernel functions , +.Ar X11 +.Pq X Window System , +.Ar X11R6 +.Pq X Window System , +.Ar unass +.Pq unassociated , +.Ar local +.Pq local system , +.Ar draft +.Pq draft manual , +or +.Ar paper +.Pq paper . +It should correspond to the manual's filename suffix and defaults to +.Dq 1 +if unspecified. +.It Ar volume +This overrides the volume inferred from +.Ar section . +This field is optional, and if specified, must be one of +.Ar USD +.Pq users' supplementary documents , +.Ar PS1 +.Pq programmers' supplementary documents , +.Ar AMD +.Pq administrators' supplementary documents , +.Ar SMM +.Pq system managers' manuals , +.Ar URM +.Pq users' reference manuals , +.Ar PRM +.Pq programmers' reference manuals , +.Ar KM +.Pq kernel manuals , +.Ar IND +.Pq master index , +.Ar MMI +.Pq master index , +.Ar LOCAL +.Pq local manuals , +.Ar LOC +.Pq local manuals , +or +.Ar CON +.Pq contributed manuals . +.It Ar arch +This specifies the machine architecture a manual page applies to, +where relevant, for example +.Cm alpha , +.Cm amd64 , +.Cm i386 , +or +.Cm sparc64 . +The list of supported architectures varies by operating system. +For the full list of all architectures recognized by +.Xr mandoc 1 , +see the file +.Pa arch.in +in the source distribution. +.El +.Pp +Examples: +.Dl \&.Dt FOO 1 +.Dl \&.Dt FOO 4 KM +.Dl \&.Dt FOO 9 i386 +.Pp +See also +.Sx \&Dd +and +.Sx \&Os . +.Ss \&Dv +Defined variables such as preprocessor constants, constant symbols, +enumeration values, and so on. +.Pp +Examples: +.Dl \&.Dv NULL +.Dl \&.Dv BUFSIZ +.Dl \&.Dv STDOUT_FILENO +.Pp +See also +.Sx \&Er +and +.Sx \&Ev +for special-purpose constants and +.Sx \&Va +for variable symbols. +.Ss \&Dx +Format the DragonFly BSD version provided as an argument, or a default +value if no argument is provided. +.Pp +Examples: +.Dl \&.Dx 2.4.1 +.Dl \&.Dx +.Pp +See also +.Sx \&At , +.Sx \&Bsx , +.Sx \&Bx , +.Sx \&Fx , +.Sx \&Nx , +.Sx \&Ox , +and +.Sx \&Ux . +.Ss \&Ec +Close a scope started by +.Sx \&Eo . +Its syntax is as follows: +.Pp +.D1 Pf \. Sx \&Ec Op Ar TERM +.Pp +The +.Ar TERM +argument is used as the enclosure tail, for example, specifying \e(rq +will emulate +.Sx \&Dc . +.Ss \&Ed +End a display context started by +.Sx \&Bd . +.Ss \&Ef +End a font mode context started by +.Sx \&Bf . +.Ss \&Ek +End a keep context started by +.Sx \&Bk . +.Ss \&El +End a list context started by +.Sx \&Bl . +.Pp +See also +.Sx \&Bl +and +.Sx \&It . +.Ss \&Em +Denotes text that should be +.Em emphasised . +Note that this is a presentation term and should not be used for +stylistically decorating technical terms. +Depending on the output device, this is usually represented +using an italic font or underlined characters. +.Pp +Examples: +.Dl \&.Em Warnings! +.Dl \&.Em Remarks : +.Pp +See also +.Sx \&Bf , +.Sx \&Li , +.Sx \&No , +and +.Sx \&Sy . +.Ss \&En +This macro is obsolete and not implemented in +.Xr mandoc 1 . +.Ss \&Eo +An arbitrary enclosure. +Its syntax is as follows: +.Pp +.D1 Pf \. Sx \&Eo Op Ar TERM +.Pp +The +.Ar TERM +argument is used as the enclosure head, for example, specifying \e(lq +will emulate +.Sx \&Do . +.Ss \&Er +Error constants for definitions of the +.Va errno +libc global variable. +This is most often used in section 2 and 3 manual pages. +.Pp +Examples: +.Dl \&.Er EPERM +.Dl \&.Er ENOENT +.Pp +See also +.Sx \&Dv +for general constants. +.Ss \&Es +This macro is obsolete and not implemented. +.Ss \&Ev +Environmental variables such as those specified in +.Xr environ 7 . +.Pp +Examples: +.Dl \&.Ev DISPLAY +.Dl \&.Ev PATH +.Pp +See also +.Sx \&Dv +for general constants. +.Ss \&Ex +Insert a standard sentence regarding command exit values of 0 on success +and >0 on failure. +This is most often used in section 1, 6, and 8 manual pages. +Its syntax is as follows: +.Pp +.D1 Pf \. Sx \&Ex Fl std Op Ar utility ... +.Pp +If +.Ar utility +is not specified, the document's name set by +.Sx \&Nm +is used. +Multiple +.Ar utility +arguments are treated as separate utilities. +.Pp +See also +.Sx \&Rv . +.Ss \&Fa +Function argument. +Its syntax is as follows: +.Bd -ragged -offset indent +.Pf \. Sx \&Fa +.Op Cm argtype +.Cm argname +.Ed +.Pp +This may be invoked for names with or without the corresponding type. +It is also used to specify the field name of a structure. +Most often, the +.Sx \&Fa +macro is used in the +.Em SYNOPSIS +within +.Sx \&Fo +section when documenting multi-line function prototypes. +If invoked with multiple arguments, the arguments are separated by a +comma. +Furthermore, if the following macro is another +.Sx \&Fa , +the last argument will also have a trailing comma. +.Pp +Examples: +.Dl \&.Fa \(dqconst char *p\(dq +.Dl \&.Fa \(dqint a\(dq \(dqint b\(dq \(dqint c\(dq +.Dl \&.Fa foo +.Pp +See also +.Sx \&Fo . +.Ss \&Fc +End a function context started by +.Sx \&Fo . +.Ss \&Fd +Historically used to document include files. +This usage has been deprecated in favour of +.Sx \&In . +Do not use this macro. +.Pp +See also +.Sx MANUAL STRUCTURE +and +.Sx \&In . +.Ss \&Fl +Command-line flag or option. +Used when listing arguments to command-line utilities. +Prints a fixed-width hyphen +.Sq \- +directly followed by each argument. +If no arguments are provided, a hyphen is printed followed by a space. +If the argument is a macro, a hyphen is prefixed to the subsequent macro +output. +.Pp +Examples: +.Dl ".Fl R Op Fl H | L | P" +.Dl ".Op Fl 1AaCcdFfgHhikLlmnopqRrSsTtux" +.Dl ".Fl type Cm d Fl name Pa CVS" +.Dl ".Fl Ar signal_number" +.Dl ".Fl o Fl" +.Pp +See also +.Sx \&Cm . +.Ss \&Fn +A function name. +Its syntax is as follows: +.Bd -ragged -offset indent +.Pf \. Ns Sx \&Fn +.Op Ar functype +.Ar funcname +.Op Oo Ar argtype Oc Ar argname +.Ed +.Pp +Function arguments are surrounded in parenthesis and +are delimited by commas. +If no arguments are specified, blank parenthesis are output. +In the +.Em SYNOPSIS +section, this macro starts a new output line, +and a blank line is automatically inserted between function definitions. +.Pp +Examples: +.Dl \&.Fn \(dqint funcname\(dq \(dqint arg0\(dq \(dqint arg1\(dq +.Dl \&.Fn funcname \(dqint arg0\(dq +.Dl \&.Fn funcname arg0 +.Pp +.Bd -literal -offset indent -compact +\&.Ft functype +\&.Fn funcname +.Ed +.Pp +When referring to a function documented in another manual page, use +.Sx \&Xr +instead. +See also +.Sx MANUAL STRUCTURE , +.Sx \&Fo , +and +.Sx \&Ft . +.Ss \&Fo +Begin a function block. +This is a multi-line version of +.Sx \&Fn . +Its syntax is as follows: +.Pp +.D1 Pf \. Sx \&Fo Ar funcname +.Pp +Invocations usually occur in the following context: +.Bd -ragged -offset indent +.Pf \. Sx \&Ft Ar functype +.br +.Pf \. Sx \&Fo Ar funcname +.br +.Pf \. Sx \&Fa Oo Ar argtype Oc Ar argname +.br +\&.\.\. +.br +.Pf \. Sx \&Fc +.Ed +.Pp +A +.Sx \&Fo +scope is closed by +.Sx \&Fc . +.Pp +See also +.Sx MANUAL STRUCTURE , +.Sx \&Fa , +.Sx \&Fc , +and +.Sx \&Ft . +.Ss \&Fr +This macro is obsolete and not implemented in +.Xr mandoc 1 . +.Pp +It was used to show function return values. +The syntax was: +.Pp +.Dl Pf . Sx \&Fr Ar value +.Ss \&Ft +A function type. +Its syntax is as follows: +.Pp +.D1 Pf \. Sx \&Ft Ar functype +.Pp +In the +.Em SYNOPSIS +section, a new output line is started after this macro. +.Pp +Examples: +.Dl \&.Ft int +.Bd -literal -offset indent -compact +\&.Ft functype +\&.Fn funcname +.Ed +.Pp +See also +.Sx MANUAL STRUCTURE , +.Sx \&Fn , +and +.Sx \&Fo . +.Ss \&Fx +Format the +.Fx +version provided as an argument, or a default value +if no argument is provided. +.Pp +Examples: +.Dl \&.Fx 7.1 +.Dl \&.Fx +.Pp +See also +.Sx \&At , +.Sx \&Bsx , +.Sx \&Bx , +.Sx \&Dx , +.Sx \&Nx , +.Sx \&Ox , +and +.Sx \&Ux . +.Ss \&Hf +This macro is not implemented in +.Xr mandoc 1 . +.Pp +It was used to include the contents of a (header) file literally. +The syntax was: +.Pp +.Dl Pf . Sx \&Hf Ar filename +.Ss \&Ic +Designate an internal or interactive command. +This is similar to +.Sx \&Cm +but used for instructions rather than values. +.Pp +Examples: +.Dl \&.Ic :wq +.Dl \&.Ic hash +.Dl \&.Ic alias +.Pp +Note that using +.Sx \&Bd Fl literal +or +.Sx \&D1 +is preferred for displaying code; the +.Sx \&Ic +macro is used when referring to specific instructions. +.Ss \&In +An +.Dq include +file. +When invoked as the first macro on an input line in the +.Em SYNOPSIS +section, the argument is displayed in angle brackets +and preceded by +.Dq #include , +and a blank line is inserted in front if there is a preceding +function declaration. +This is most often used in section 2, 3, and 9 manual pages. +.Pp +Examples: +.Dl \&.In sys/types.h +.Pp +See also +.Sx MANUAL STRUCTURE . +.Ss \&It +A list item. +The syntax of this macro depends on the list type. +.Pp +Lists +of type +.Fl hang , +.Fl ohang , +.Fl inset , +and +.Fl diag +have the following syntax: +.Pp +.D1 Pf \. Sx \&It Ar args +.Pp +Lists of type +.Fl bullet , +.Fl dash , +.Fl enum , +.Fl hyphen +and +.Fl item +have the following syntax: +.Pp +.D1 Pf \. Sx \&It +.Pp +with subsequent lines interpreted within the scope of the +.Sx \&It +until either a closing +.Sx \&El +or another +.Sx \&It . +.Pp +The +.Fl tag +list has the following syntax: +.Pp +.D1 Pf \. Sx \&It Op Cm args +.Pp +Subsequent lines are interpreted as with +.Fl bullet +and family. +The line arguments correspond to the list's left-hand side; body +arguments correspond to the list's contents. +.Pp +The +.Fl column +list is the most complicated. +Its syntax is as follows: +.Pp +.D1 Pf \. Sx \&It Ar cell Op <TAB> Ar cell ... +.D1 Pf \. Sx \&It Ar cell Op Sx \&Ta Ar cell ... +.Pp +The arguments consist of one or more lines of text and macros +representing a complete table line. +Cells within the line are delimited by tabs or by the special +.Sx \&Ta +block macro. +The tab cell delimiter may only be used within the +.Sx \&It +line itself; on following lines, only the +.Sx \&Ta +macro can be used to delimit cells, and +.Sx \&Ta +is only recognised as a macro when called by other macros, +not as the first macro on a line. +.Pp +Note that quoted strings may span tab-delimited cells on an +.Sx \&It +line. +For example, +.Pp +.Dl .It \(dqcol1 ; <TAB> col2 ;\(dq \&; +.Pp +will preserve the semicolon whitespace except for the last. +.Pp +See also +.Sx \&Bl . +.Ss \&Lb +Specify a library. +The syntax is as follows: +.Pp +.D1 Pf \. Sx \&Lb Ar library +.Pp +The +.Ar library +parameter may be a system library, such as +.Cm libz +or +.Cm libpam , +in which case a small library description is printed next to the linker +invocation; or a custom library, in which case the library name is +printed in quotes. +This is most commonly used in the +.Em SYNOPSIS +section as described in +.Sx MANUAL STRUCTURE . +.Pp +Examples: +.Dl \&.Lb libz +.Dl \&.Lb mdoc +.Ss \&Li +Denotes text that should be in a +.Li literal +font mode. +Note that this is a presentation term and should not be used for +stylistically decorating technical terms. +.Pp +On terminal output devices, this is often indistinguishable from +normal text. +.Pp +See also +.Sx \&Bf , +.Sx \&Em , +.Sx \&No , +and +.Sx \&Sy . +.Ss \&Lk +Format a hyperlink. +Its syntax is as follows: +.Pp +.D1 Pf \. Sx \&Lk Ar uri Op Ar name +.Pp +Examples: +.Dl \&.Lk http://bsd.lv \(dqThe BSD.lv Project\(dq +.Dl \&.Lk http://bsd.lv +.Pp +See also +.Sx \&Mt . +.Ss \&Lp +Synonym for +.Sx \&Pp . +.Ss \&Ms +Display a mathematical symbol. +Its syntax is as follows: +.Pp +.D1 Pf \. Sx \&Ms Ar symbol +.Pp +Examples: +.Dl \&.Ms sigma +.Dl \&.Ms aleph +.Ss \&Mt +Format a +.Dq mailto: +hyperlink. +Its syntax is as follows: +.Pp +.D1 Pf \. Sx \&Mt Ar address +.Pp +Examples: +.Dl \&.Mt discuss@manpages.bsd.lv +.Ss \&Nd +A one line description of the manual's content. +This may only be invoked in the +.Em SYNOPSIS +section subsequent the +.Sx \&Nm +macro. +.Pp +Examples: +.Dl Pf . Sx \&Nd mdoc language reference +.Dl Pf . Sx \&Nd format and display UNIX manuals +.Pp +The +.Sx \&Nd +macro technically accepts child macros and terminates with a subsequent +.Sx \&Sh +invocation. +Do not assume this behaviour: some +.Xr whatis 1 +database generators are not smart enough to parse more than the line +arguments and will display macros verbatim. +.Pp +See also +.Sx \&Nm . +.Ss \&Nm +The name of the manual page, or \(em in particular in section 1, 6, +and 8 pages \(em of an additional command or feature documented in +the manual page. +When first invoked, the +.Sx \&Nm +macro expects a single argument, the name of the manual page. +Usually, the first invocation happens in the +.Em NAME +section of the page. +The specified name will be remembered and used whenever the macro is +called again without arguments later in the page. +The +.Sx \&Nm +macro uses +.Sx Block full-implicit +semantics when invoked as the first macro on an input line in the +.Em SYNOPSIS +section; otherwise, it uses ordinary +.Sx In-line +semantics. +.Pp +Examples: +.Bd -literal -offset indent +\&.Sh SYNOPSIS +\&.Nm cat +\&.Op Fl benstuv +\&.Op Ar +.Ed +.Pp +In the +.Em SYNOPSIS +of section 2, 3 and 9 manual pages, use the +.Sx \&Fn +macro rather than +.Sx \&Nm +to mark up the name of the manual page. +.Ss \&No +Normal text. +Closes the scope of any preceding in-line macro. +When used after physical formatting macros like +.Sx \&Em +or +.Sx \&Sy , +switches back to the standard font face and weight. +Can also be used to embed plain text strings in macro lines +using semantic annotation macros. +.Pp +Examples: +.Dl ".Em italic , Sy bold , No and roman" +.Pp +.Bd -literal -offset indent -compact +\&.Sm off +\&.Cm :C No / Ar pattern No / Ar replacement No / +\&.Sm on +.Ed +.Pp +See also +.Sx \&Em , +.Sx \&Li , +and +.Sx \&Sy . +.Ss \&Ns +Suppress a space between the output of the preceding macro +and the following text or macro. +Following invocation, input is interpreted as normal text +just like after an +.Sx \&No +macro. +.Pp +This has no effect when invoked at the start of a macro line. +.Pp +Examples: +.Dl ".Ar name Ns = Ns Ar value" +.Dl ".Cm :M Ns Ar pattern" +.Dl ".Fl o Ns Ar output" +.Pp +See also +.Sx \&No +and +.Sx \&Sm . +.Ss \&Nx +Format the +.Nx +version provided as an argument, or a default value if +no argument is provided. +.Pp +Examples: +.Dl \&.Nx 5.01 +.Dl \&.Nx +.Pp +See also +.Sx \&At , +.Sx \&Bsx , +.Sx \&Bx , +.Sx \&Dx , +.Sx \&Fx , +.Sx \&Ox , +and +.Sx \&Ux . +.Ss \&Oc +Close multi-line +.Sx \&Oo +context. +.Ss \&Oo +Multi-line version of +.Sx \&Op . +.Pp +Examples: +.Bd -literal -offset indent -compact +\&.Oo +\&.Op Fl flag Ns Ar value +\&.Oc +.Ed +.Ss \&Op +Optional part of a command line. +Prints the argument(s) in brackets. +This is most often used in the +.Em SYNOPSIS +section of section 1 and 8 manual pages. +.Pp +Examples: +.Dl \&.Op \&Fl a \&Ar b +.Dl \&.Op \&Ar a | b +.Pp +See also +.Sx \&Oo . +.Ss \&Os +Document operating system version. +This is the mandatory third macro of +any +.Nm +file. +Its syntax is as follows: +.Pp +.D1 Pf \. Sx \&Os Op Ar system Op Ar version +.Pp +The optional +.Ar system +parameter specifies the relevant operating system or environment. +Left unspecified, it defaults to the local operating system version. +This is the suggested form. +.Pp +Examples: +.Dl \&.Os +.Dl \&.Os KTH/CSC/TCS +.Dl \&.Os BSD 4.3 +.Pp +See also +.Sx \&Dd +and +.Sx \&Dt . +.Ss \&Ot +This macro is obsolete and not implemented in +.Xr mandoc 1 . +.Pp +Historical +.Xr mdoc 7 +packages described it as +.Dq "old function type (FORTRAN)" . +.Ss \&Ox +Format the +.Ox +version provided as an argument, or a default value +if no argument is provided. +.Pp +Examples: +.Dl \&.Ox 4.5 +.Dl \&.Ox +.Pp +See also +.Sx \&At , +.Sx \&Bsx , +.Sx \&Bx , +.Sx \&Dx , +.Sx \&Fx , +.Sx \&Nx , +and +.Sx \&Ux . +.Ss \&Pa +An absolute or relative file system path, or a file or directory name. +If an argument is not provided, the character +.Sq \(ti +is used as a default. +.Pp +Examples: +.Dl \&.Pa /usr/bin/mandoc +.Dl \&.Pa /usr/share/man/man7/mdoc.7 +.Pp +See also +.Sx \&Lk . +.Ss \&Pc +Close parenthesised context opened by +.Sx \&Po . +.Ss \&Pf +Removes the space between its argument +.Pq Dq prefix +and the following macro. +Its syntax is as follows: +.Pp +.D1 .Pf Ar prefix macro arguments ... +.Pp +This is equivalent to: +.Pp +.D1 .No Ar prefix No \&Ns Ar macro arguments ... +.Pp +Examples: +.Dl ".Pf $ Ar variable_name" +.Dl ".Pf 0x Ar hex_digits" +.Pp +See also +.Sx \&Ns +and +.Sx \&Sm . +.Ss \&Po +Multi-line version of +.Sx \&Pq . +.Ss \&Pp +Break a paragraph. +This will assert vertical space between prior and subsequent macros +and/or text. +.Pp +Paragraph breaks are not needed before or after +.Sx \&Sh +or +.Sx \&Ss +macros or before displays +.Pq Sx \&Bd +or lists +.Pq Sx \&Bl +unless the +.Fl compact +flag is given. +.Ss \&Pq +Parenthesised enclosure. +.Pp +See also +.Sx \&Po . +.Ss \&Qc +Close quoted context opened by +.Sx \&Qo . +.Ss \&Ql +Format a single-quoted literal. +See also +.Sx \&Qq +and +.Sx \&Sq . +.Ss \&Qo +Multi-line version of +.Sx \&Qq . +.Ss \&Qq +Encloses its arguments in +.Qq typewriter +double-quotes. +Consider using +.Sx \&Dq . +.Pp +See also +.Sx \&Dq , +.Sx \&Sq , +and +.Sx \&Qo . +.Ss \&Re +Close an +.Sx \&Rs +block. +Does not have any tail arguments. +.Ss \&Rs +Begin a bibliographic +.Pq Dq reference +block. +Does not have any head arguments. +The block macro may only contain +.Sx \&%A , +.Sx \&%B , +.Sx \&%C , +.Sx \&%D , +.Sx \&%I , +.Sx \&%J , +.Sx \&%N , +.Sx \&%O , +.Sx \&%P , +.Sx \&%Q , +.Sx \&%R , +.Sx \&%T , +.Sx \&%U , +and +.Sx \&%V +child macros (at least one must be specified). +.Pp +Examples: +.Bd -literal -offset indent -compact +\&.Rs +\&.%A J. E. Hopcroft +\&.%A J. D. Ullman +\&.%B Introduction to Automata Theory, Languages, and Computation +\&.%I Addison-Wesley +\&.%C Reading, Massachusettes +\&.%D 1979 +\&.Re +.Ed +.Pp +If an +.Sx \&Rs +block is used within a SEE ALSO section, a vertical space is asserted +before the rendered output, else the block continues on the current +line. +.Ss \&Rv +Insert a standard sentence regarding a function call's return value of 0 +on success and \-1 on error, with the +.Va errno +libc global variable set on error. +Its syntax is as follows: +.Pp +.D1 Pf \. Sx \&Rv Fl std Op Ar function ... +.Pp +If +.Ar function +is not specified, the document's name set by +.Sx \&Nm +is used. +Multiple +.Ar function +arguments are treated as separate functions. +.Pp +See also +.Sx \&Ex . +.Ss \&Sc +Close single-quoted context opened by +.Sx \&So . +.Ss \&Sh +Begin a new section. +For a list of conventional manual sections, see +.Sx MANUAL STRUCTURE . +These sections should be used unless it's absolutely necessary that +custom sections be used. +.Pp +Section names should be unique so that they may be keyed by +.Sx \&Sx . +Although this macro is parsed, it should not consist of child node or it +may not be linked with +.Sx \&Sx . +.Pp +See also +.Sx \&Pp , +.Sx \&Ss , +and +.Sx \&Sx . +.Ss \&Sm +Switches the spacing mode for output generated from macros. +Its syntax is as follows: +.Pp +.D1 Pf \. Sx \&Sm Cm on | off +.Pp +By default, spacing is +.Cm on . +When switched +.Cm off , +no white space is inserted between macro arguments and between the +output generated from adjacent macros, but text lines +still get normal spacing between words and sentences. +.Ss \&So +Multi-line version of +.Sx \&Sq . +.Ss \&Sq +Encloses its arguments in +.Sq typewriter +single-quotes. +.Pp +See also +.Sx \&Dq , +.Sx \&Qq , +and +.Sx \&So . +.Ss \&Ss +Begin a new subsection. +Unlike with +.Sx \&Sh , +there is no convention for the naming of subsections. +Except +.Em DESCRIPTION , +the conventional sections described in +.Sx MANUAL STRUCTURE +rarely have subsections. +.Pp +Sub-section names should be unique so that they may be keyed by +.Sx \&Sx . +Although this macro is parsed, it should not consist of child node or it +may not be linked with +.Sx \&Sx . +.Pp +See also +.Sx \&Pp , +.Sx \&Sh , +and +.Sx \&Sx . +.Ss \&St +Replace an abbreviation for a standard with the full form. +The following standards are recognised: +.Pp +.Bl -tag -width "-p1003.1g-2000X" -compact +.It \-p1003.1-88 +.St -p1003.1-88 +.It \-p1003.1-90 +.St -p1003.1-90 +.It \-p1003.1-96 +.St -p1003.1-96 +.It \-p1003.1-2001 +.St -p1003.1-2001 +.It \-p1003.1-2004 +.St -p1003.1-2004 +.It \-p1003.1-2008 +.St -p1003.1-2008 +.It \-p1003.1 +.St -p1003.1 +.It \-p1003.1b +.St -p1003.1b +.It \-p1003.1b-93 +.St -p1003.1b-93 +.It \-p1003.1c-95 +.St -p1003.1c-95 +.It \-p1003.1g-2000 +.St -p1003.1g-2000 +.It \-p1003.1i-95 +.St -p1003.1i-95 +.It \-p1003.2-92 +.St -p1003.2-92 +.It \-p1003.2a-92 +.St -p1003.2a-92 +.It \-p1387.2-95 +.St -p1387.2-95 +.It \-p1003.2 +.St -p1003.2 +.It \-p1387.2 +.St -p1387.2 +.It \-isoC +.St -isoC +.It \-isoC-90 +.St -isoC-90 +.It \-isoC-amd1 +.St -isoC-amd1 +.It \-isoC-tcor1 +.St -isoC-tcor1 +.It \-isoC-tcor2 +.St -isoC-tcor2 +.It \-isoC-99 +.St -isoC-99 +.It \-isoC-2011 +.St -isoC-2011 +.It \-iso9945-1-90 +.St -iso9945-1-90 +.It \-iso9945-1-96 +.St -iso9945-1-96 +.It \-iso9945-2-93 +.St -iso9945-2-93 +.It \-ansiC +.St -ansiC +.It \-ansiC-89 +.St -ansiC-89 +.It \-ansiC-99 +.St -ansiC-99 +.It \-ieee754 +.St -ieee754 +.It \-iso8802-3 +.St -iso8802-3 +.It \-iso8601 +.St -iso8601 +.It \-ieee1275-94 +.St -ieee1275-94 +.It \-xpg3 +.St -xpg3 +.It \-xpg4 +.St -xpg4 +.It \-xpg4.2 +.St -xpg4.2 +.It \-xpg4.3 +.St -xpg4.3 +.It \-xbd5 +.St -xbd5 +.It \-xcu5 +.St -xcu5 +.It \-xsh5 +.St -xsh5 +.It \-xns5 +.St -xns5 +.It \-xns5.2 +.St -xns5.2 +.It \-xns5.2d2.0 +.St -xns5.2d2.0 +.It \-xcurses4.2 +.St -xcurses4.2 +.It \-susv2 +.St -susv2 +.It \-susv3 +.St -susv3 +.It \-svid4 +.St -svid4 +.El +.Ss \&Sx +Reference a section or subsection in the same manual page. +The referenced section or subsection name must be identical to the +enclosed argument, including whitespace. +.Pp +Examples: +.Dl \&.Sx MANUAL STRUCTURE +.Pp +See also +.Sx \&Sh +and +.Sx \&Ss . +.Ss \&Sy +Format enclosed arguments in symbolic +.Pq Dq boldface . +Note that this is a presentation term and should not be used for +stylistically decorating technical terms. +.Pp +See also +.Sx \&Bf , +.Sx \&Em , +.Sx \&Li , +and +.Sx \&No . +.Ss \&Ta +Table cell separator in +.Sx \&Bl Fl column +lists; can only be used below +.Sx \&It . +.Ss \&Tn +Format a tradename. +.Pp +Since this macro is often implemented to use a small caps font, +it has historically been used for acronyms (like ASCII) as well. +Such usage is not recommended because it would use the same macro +sometimes for semantical annotation, sometimes for physical formatting. +.Pp +Examples: +.Dl \&.Tn IBM +.Ss \&Ud +Prints out +.Dq currently under development. +.Ss \&Ux +Format the UNIX name. +Accepts no argument. +.Pp +Examples: +.Dl \&.Ux +.Pp +See also +.Sx \&At , +.Sx \&Bsx , +.Sx \&Bx , +.Sx \&Dx , +.Sx \&Fx , +.Sx \&Nx , +and +.Sx \&Ox . +.Ss \&Va +A variable name. +.Pp +Examples: +.Dl \&.Va foo +.Dl \&.Va const char *bar ; +.Ss \&Vt +A variable type. +This is also used for indicating global variables in the +.Em SYNOPSIS +section, in which case a variable name is also specified. +Note that it accepts +.Sx Block partial-implicit +syntax when invoked as the first macro on an input line in the +.Em SYNOPSIS +section, else it accepts ordinary +.Sx In-line +syntax. +In the former case, this macro starts a new output line, +and a blank line is inserted in front if there is a preceding +function definition or include directive. +.Pp +Note that this should not be confused with +.Sx \&Ft , +which is used for function return types. +.Pp +Examples: +.Dl \&.Vt unsigned char +.Dl \&.Vt extern const char * const sys_signame[] \&; +.Pp +See also +.Sx MANUAL STRUCTURE +and +.Sx \&Va . +.Ss \&Xc +Close a scope opened by +.Sx \&Xo . +.Ss \&Xo +Extend the header of an +.Sx \&It +macro or the body of a partial-implicit block macro +beyond the end of the input line. +This macro originally existed to work around the 9-argument limit +of historic +.Xr roff 7 . +.Ss \&Xr +Link to another manual +.Pq Qq cross-reference . +Its syntax is as follows: +.Pp +.D1 Pf \. Sx \&Xr Ar name section +.Pp +The +.Ar name +and +.Ar section +are the name and section of the linked manual. +If +.Ar section +is followed by non-punctuation, an +.Sx \&Ns +is inserted into the token stream. +This behaviour is for compatibility with +GNU troff. +.Pp +Examples: +.Dl \&.Xr mandoc 1 +.Dl \&.Xr mandoc 1 \&; +.Dl \&.Xr mandoc 1 \&Ns s behaviour +.Ss \&br +Emits a line-break. +This macro should not be used; it is implemented for compatibility with +historical manuals. +.Pp +Consider using +.Sx \&Pp +in the event of natural paragraph breaks. +.Ss \&sp +Emits vertical space. +This macro should not be used; it is implemented for compatibility with +historical manuals. +Its syntax is as follows: +.Pp +.D1 Pf \. Sx \&sp Op Ar height +.Pp +The +.Ar height +argument must be formatted as described in +.Sx Scaling Widths . +If unspecified, +.Sx \&sp +asserts a single vertical space. +.Sh MACRO SYNTAX +The syntax of a macro depends on its classification. +In this section, +.Sq \-arg +refers to macro arguments, which may be followed by zero or more +.Sq parm +parameters; +.Sq \&Yo +opens the scope of a macro; and if specified, +.Sq \&Yc +closes it out. +.Pp +The +.Em Callable +column indicates that the macro may also be called by passing its name +as an argument to another macro. +For example, +.Sq \&.Op \&Fl O \&Ar file +produces +.Sq Op Fl O Ar file . +To prevent a macro call and render the macro name literally, +escape it by prepending a zero-width space, +.Sq \e& . +For example, +.Sq \&Op \e&Fl O +produces +.Sq Op \&Fl O . +If a macro is not callable but its name appears as an argument +to another macro, it is interpreted as opaque text. +For example, +.Sq \&.Fl \&Sh +produces +.Sq Fl \&Sh . +.Pp +The +.Em Parsed +column indicates whether the macro may call other macros by receiving +their names as arguments. +If a macro is not parsed but the name of another macro appears +as an argument, it is interpreted as opaque text. +.Pp +The +.Em Scope +column, if applicable, describes closure rules. +.Ss Block full-explicit +Multi-line scope closed by an explicit closing macro. +All macros contains bodies; only +.Sx \&Bf +and +.Pq optionally +.Sx \&Bl +contain a head. +.Bd -literal -offset indent +\&.Yo \(lB\-arg \(lBparm...\(rB\(rB \(lBhead...\(rB +\(lBbody...\(rB +\&.Yc +.Ed +.Bl -column "MacroX" "CallableX" "ParsedX" "closed by XXX" -offset indent +.It Em Macro Ta Em Callable Ta Em Parsed Ta Em Scope +.It Sx \&Bd Ta \&No Ta \&No Ta closed by Sx \&Ed +.It Sx \&Bf Ta \&No Ta \&No Ta closed by Sx \&Ef +.It Sx \&Bk Ta \&No Ta \&No Ta closed by Sx \&Ek +.It Sx \&Bl Ta \&No Ta \&No Ta closed by Sx \&El +.It Sx \&Ed Ta \&No Ta \&No Ta opened by Sx \&Bd +.It Sx \&Ef Ta \&No Ta \&No Ta opened by Sx \&Bf +.It Sx \&Ek Ta \&No Ta \&No Ta opened by Sx \&Bk +.It Sx \&El Ta \&No Ta \&No Ta opened by Sx \&Bl +.El +.Ss Block full-implicit +Multi-line scope closed by end-of-file or implicitly by another macro. +All macros have bodies; some +.Po +.Sx \&It Fl bullet , +.Fl hyphen , +.Fl dash , +.Fl enum , +.Fl item +.Pc +don't have heads; only one +.Po +.Sx \&It +in +.Sx \&Bl Fl column +.Pc +has multiple heads. +.Bd -literal -offset indent +\&.Yo \(lB\-arg \(lBparm...\(rB\(rB \(lBhead... \(lBTa head...\(rB\(rB +\(lBbody...\(rB +.Ed +.Bl -column "MacroX" "CallableX" "ParsedX" "closed by XXXXXXXXXXX" -offset indent +.It Em Macro Ta Em Callable Ta Em Parsed Ta Em Scope +.It Sx \&It Ta \&No Ta Yes Ta closed by Sx \&It , Sx \&El +.It Sx \&Nd Ta \&No Ta \&No Ta closed by Sx \&Sh +.It Sx \&Nm Ta \&No Ta Yes Ta closed by Sx \&Nm , Sx \&Sh , Sx \&Ss +.It Sx \&Sh Ta \&No Ta Yes Ta closed by Sx \&Sh +.It Sx \&Ss Ta \&No Ta Yes Ta closed by Sx \&Sh , Sx \&Ss +.El +.Pp +Note that the +.Sx \&Nm +macro is a +.Sx Block full-implicit +macro only when invoked as the first macro +in a +.Em SYNOPSIS +section line, else it is +.Sx In-line . +.Ss Block partial-explicit +Like block full-explicit, but also with single-line scope. +Each has at least a body and, in limited circumstances, a head +.Po +.Sx \&Fo , +.Sx \&Eo +.Pc +and/or tail +.Pq Sx \&Ec . +.Bd -literal -offset indent +\&.Yo \(lB\-arg \(lBparm...\(rB\(rB \(lBhead...\(rB +\(lBbody...\(rB +\&.Yc \(lBtail...\(rB + +\&.Yo \(lB\-arg \(lBparm...\(rB\(rB \(lBhead...\(rB \ +\(lBbody...\(rB \&Yc \(lBtail...\(rB +.Ed +.Bl -column "MacroX" "CallableX" "ParsedX" "closed by XXXX" -offset indent +.It Em Macro Ta Em Callable Ta Em Parsed Ta Em Scope +.It Sx \&Ac Ta Yes Ta Yes Ta opened by Sx \&Ao +.It Sx \&Ao Ta Yes Ta Yes Ta closed by Sx \&Ac +.It Sx \&Bc Ta Yes Ta Yes Ta closed by Sx \&Bo +.It Sx \&Bo Ta Yes Ta Yes Ta opened by Sx \&Bc +.It Sx \&Brc Ta Yes Ta Yes Ta opened by Sx \&Bro +.It Sx \&Bro Ta Yes Ta Yes Ta closed by Sx \&Brc +.It Sx \&Dc Ta Yes Ta Yes Ta opened by Sx \&Do +.It Sx \&Do Ta Yes Ta Yes Ta closed by Sx \&Dc +.It Sx \&Ec Ta Yes Ta Yes Ta opened by Sx \&Eo +.It Sx \&Eo Ta Yes Ta Yes Ta closed by Sx \&Ec +.It Sx \&Fc Ta Yes Ta Yes Ta opened by Sx \&Fo +.It Sx \&Fo Ta \&No Ta \&No Ta closed by Sx \&Fc +.It Sx \&Oc Ta Yes Ta Yes Ta closed by Sx \&Oo +.It Sx \&Oo Ta Yes Ta Yes Ta opened by Sx \&Oc +.It Sx \&Pc Ta Yes Ta Yes Ta closed by Sx \&Po +.It Sx \&Po Ta Yes Ta Yes Ta opened by Sx \&Pc +.It Sx \&Qc Ta Yes Ta Yes Ta opened by Sx \&Oo +.It Sx \&Qo Ta Yes Ta Yes Ta closed by Sx \&Oc +.It Sx \&Re Ta \&No Ta \&No Ta opened by Sx \&Rs +.It Sx \&Rs Ta \&No Ta \&No Ta closed by Sx \&Re +.It Sx \&Sc Ta Yes Ta Yes Ta opened by Sx \&So +.It Sx \&So Ta Yes Ta Yes Ta closed by Sx \&Sc +.It Sx \&Xc Ta Yes Ta Yes Ta opened by Sx \&Xo +.It Sx \&Xo Ta Yes Ta Yes Ta closed by Sx \&Xc +.El +.Ss Block partial-implicit +Like block full-implicit, but with single-line scope closed by the +end of the line. +.Bd -literal -offset indent +\&.Yo \(lB\-arg \(lBval...\(rB\(rB \(lBbody...\(rB \(lBres...\(rB +.Ed +.Bl -column "MacroX" "CallableX" "ParsedX" -offset indent +.It Em Macro Ta Em Callable Ta Em Parsed +.It Sx \&Aq Ta Yes Ta Yes +.It Sx \&Bq Ta Yes Ta Yes +.It Sx \&Brq Ta Yes Ta Yes +.It Sx \&D1 Ta \&No Ta \&Yes +.It Sx \&Dl Ta \&No Ta Yes +.It Sx \&Dq Ta Yes Ta Yes +.It Sx \&Op Ta Yes Ta Yes +.It Sx \&Pq Ta Yes Ta Yes +.It Sx \&Ql Ta Yes Ta Yes +.It Sx \&Qq Ta Yes Ta Yes +.It Sx \&Sq Ta Yes Ta Yes +.It Sx \&Vt Ta Yes Ta Yes +.El +.Pp +Note that the +.Sx \&Vt +macro is a +.Sx Block partial-implicit +only when invoked as the first macro +in a +.Em SYNOPSIS +section line, else it is +.Sx In-line . +.Ss Special block macro +The +.Sx \&Ta +macro can only be used below +.Sx \&It +in +.Sx \&Bl Fl column +lists. +It delimits blocks representing table cells; +these blocks have bodies, but no heads. +.Bl -column "MacroX" "CallableX" "ParsedX" "closed by XXXX" -offset indent +.It Em Macro Ta Em Callable Ta Em Parsed Ta Em Scope +.It Sx \&Ta Ta Yes Ta Yes Ta closed by Sx \&Ta , Sx \&It +.El +.Ss In-line +Closed by the end of the line, fixed argument lengths, +and/or subsequent macros. +In-line macros have only text children. +If a number (or inequality) of arguments is +.Pq n , +then the macro accepts an arbitrary number of arguments. +.Bd -literal -offset indent +\&.Yo \(lB\-arg \(lBval...\(rB\(rB \(lBargs...\(rB \(lBres...\(rB + +\&.Yo \(lB\-arg \(lBval...\(rB\(rB \(lBargs...\(rB Yc... + +\&.Yo \(lB\-arg \(lBval...\(rB\(rB arg0 arg1 argN +.Ed +.Bl -column "MacroX" "CallableX" "ParsedX" "Arguments" -offset indent +.It Em Macro Ta Em Callable Ta Em Parsed Ta Em Arguments +.It Sx \&%A Ta \&No Ta \&No Ta >0 +.It Sx \&%B Ta \&No Ta \&No Ta >0 +.It Sx \&%C Ta \&No Ta \&No Ta >0 +.It Sx \&%D Ta \&No Ta \&No Ta >0 +.It Sx \&%I Ta \&No Ta \&No Ta >0 +.It Sx \&%J Ta \&No Ta \&No Ta >0 +.It Sx \&%N Ta \&No Ta \&No Ta >0 +.It Sx \&%O Ta \&No Ta \&No Ta >0 +.It Sx \&%P Ta \&No Ta \&No Ta >0 +.It Sx \&%Q Ta \&No Ta \&No Ta >0 +.It Sx \&%R Ta \&No Ta \&No Ta >0 +.It Sx \&%T Ta \&No Ta \&No Ta >0 +.It Sx \&%U Ta \&No Ta \&No Ta >0 +.It Sx \&%V Ta \&No Ta \&No Ta >0 +.It Sx \&Ad Ta Yes Ta Yes Ta >0 +.It Sx \&An Ta Yes Ta Yes Ta >0 +.It Sx \&Ap Ta Yes Ta Yes Ta 0 +.It Sx \&Ar Ta Yes Ta Yes Ta n +.It Sx \&At Ta Yes Ta Yes Ta 1 +.It Sx \&Bsx Ta Yes Ta Yes Ta n +.It Sx \&Bt Ta \&No Ta \&No Ta 0 +.It Sx \&Bx Ta Yes Ta Yes Ta n +.It Sx \&Cd Ta Yes Ta Yes Ta >0 +.It Sx \&Cm Ta Yes Ta Yes Ta >0 +.It Sx \&Db Ta \&No Ta \&No Ta 1 +.It Sx \&Dd Ta \&No Ta \&No Ta n +.It Sx \&Dt Ta \&No Ta \&No Ta n +.It Sx \&Dv Ta Yes Ta Yes Ta >0 +.It Sx \&Dx Ta Yes Ta Yes Ta n +.It Sx \&Em Ta Yes Ta Yes Ta >0 +.It Sx \&En Ta \&No Ta \&No Ta 0 +.It Sx \&Er Ta Yes Ta Yes Ta >0 +.It Sx \&Es Ta \&No Ta \&No Ta 0 +.It Sx \&Ev Ta Yes Ta Yes Ta >0 +.It Sx \&Ex Ta \&No Ta \&No Ta n +.It Sx \&Fa Ta Yes Ta Yes Ta >0 +.It Sx \&Fd Ta \&No Ta \&No Ta >0 +.It Sx \&Fl Ta Yes Ta Yes Ta n +.It Sx \&Fn Ta Yes Ta Yes Ta >0 +.It Sx \&Fr Ta \&No Ta \&No Ta n +.It Sx \&Ft Ta Yes Ta Yes Ta >0 +.It Sx \&Fx Ta Yes Ta Yes Ta n +.It Sx \&Hf Ta \&No Ta \&No Ta n +.It Sx \&Ic Ta Yes Ta Yes Ta >0 +.It Sx \&In Ta \&No Ta \&No Ta 1 +.It Sx \&Lb Ta \&No Ta \&No Ta 1 +.It Sx \&Li Ta Yes Ta Yes Ta >0 +.It Sx \&Lk Ta Yes Ta Yes Ta >0 +.It Sx \&Lp Ta \&No Ta \&No Ta 0 +.It Sx \&Ms Ta Yes Ta Yes Ta >0 +.It Sx \&Mt Ta Yes Ta Yes Ta >0 +.It Sx \&Nm Ta Yes Ta Yes Ta n +.It Sx \&No Ta Yes Ta Yes Ta 0 +.It Sx \&Ns Ta Yes Ta Yes Ta 0 +.It Sx \&Nx Ta Yes Ta Yes Ta n +.It Sx \&Os Ta \&No Ta \&No Ta n +.It Sx \&Ot Ta \&No Ta \&No Ta n +.It Sx \&Ox Ta Yes Ta Yes Ta n +.It Sx \&Pa Ta Yes Ta Yes Ta n +.It Sx \&Pf Ta Yes Ta Yes Ta 1 +.It Sx \&Pp Ta \&No Ta \&No Ta 0 +.It Sx \&Rv Ta \&No Ta \&No Ta n +.It Sx \&Sm Ta \&No Ta \&No Ta 1 +.It Sx \&St Ta \&No Ta Yes Ta 1 +.It Sx \&Sx Ta Yes Ta Yes Ta >0 +.It Sx \&Sy Ta Yes Ta Yes Ta >0 +.It Sx \&Tn Ta Yes Ta Yes Ta >0 +.It Sx \&Ud Ta \&No Ta \&No Ta 0 +.It Sx \&Ux Ta Yes Ta Yes Ta n +.It Sx \&Va Ta Yes Ta Yes Ta n +.It Sx \&Vt Ta Yes Ta Yes Ta >0 +.It Sx \&Xr Ta Yes Ta Yes Ta >0 +.It Sx \&br Ta \&No Ta \&No Ta 0 +.It Sx \&sp Ta \&No Ta \&No Ta 1 +.El +.Ss Delimiters +When a macro argument consists of one single input character +considered as a delimiter, the argument gets special handling. +This does not apply when delimiters appear in arguments containing +more than one character. +Consequently, to prevent special handling and just handle it +like any other argument, a delimiter can be escaped by prepending +a zero-width space +.Pq Sq \e& . +In text lines, delimiters never need escaping, but may be used +as normal punctuation. +.Pp +For many macros, when the leading arguments are opening delimiters, +these delimiters are put before the macro scope, +and when the trailing arguments are closing delimiters, +these delimiters are put after the macro scope. +For example, +.Pp +.D1 Pf \. \&Aq "( [ word ] ) ." +.Pp +renders as: +.Pp +.D1 Aq ( [ word ] ) . +.Pp +Opening delimiters are: +.Pp +.Bl -tag -width Ds -offset indent -compact +.It \&( +left parenthesis +.It \&[ +left bracket +.El +.Pp +Closing delimiters are: +.Pp +.Bl -tag -width Ds -offset indent -compact +.It \&. +period +.It \&, +comma +.It \&: +colon +.It \&; +semicolon +.It \&) +right parenthesis +.It \&] +right bracket +.It \&? +question mark +.It \&! +exclamation mark +.El +.Pp +Note that even a period preceded by a backslash +.Pq Sq \e.\& +gets this special handling; use +.Sq \e&. +to prevent that. +.Pp +Many in-line macros interrupt their scope when they encounter +delimiters, and resume their scope when more arguments follow that +are not delimiters. +For example, +.Pp +.D1 Pf \. \&Fl "a ( b | c \e*(Ba d ) e" +.Pp +renders as: +.Pp +.D1 Fl a ( b | c \*(Ba d ) e +.Pp +This applies to both opening and closing delimiters, +and also to the middle delimiter: +.Pp +.Bl -tag -width Ds -offset indent -compact +.It \&| +vertical bar +.El +.Pp +As a special case, the predefined string \e*(Ba is handled and rendered +in the same way as a plain +.Sq \&| +character. +Using this predefined string is not recommended in new manuals. +.Ss Font handling +In +.Nm +documents, usage of semantic markup is recommended in order to have +proper fonts automatically selected; only when no fitting semantic markup +is available, consider falling back to +.Sx Physical markup +macros. +Whenever any +.Nm +macro switches the +.Xr roff 7 +font mode, it will automatically restore the previous font when exiting +its scope. +Manually switching the font using the +.Xr roff 7 +.Ql \ef +font escape sequences is never required. +.Sh COMPATIBILITY +This section documents compatibility between mandoc and other other +troff implementations, at this time limited to GNU troff +.Pq Qq groff . +The term +.Qq historic groff +refers to groff versions before 1.17, +which featured a significant update of the +.Pa doc.tmac +file. +.Pp +Heirloom troff, the other significant troff implementation accepting +\-mdoc, is similar to historic groff. +.Pp +The following problematic behaviour is found in groff: +.ds hist (Historic groff only.) +.Pp +.Bl -dash -compact +.It +Display macros +.Po +.Sx \&Bd , +.Sx \&Dl , +and +.Sx \&D1 +.Pc +may not be nested. +\*[hist] +.It +.Sx \&At +with unknown arguments produces no output at all. +\*[hist] +Newer groff and mandoc print +.Qq AT&T UNIX +and the arguments. +.It +.Sx \&Bl Fl column +does not recognise trailing punctuation characters when they immediately +precede tabulator characters, but treats them as normal text and +outputs a space before them. +.It +.Sx \&Bd Fl ragged compact +does not start a new line. +\*[hist] +.It +.Sx \&Dd +with non-standard arguments behaves very strangely. +When there are three arguments, they are printed verbatim. +Any other number of arguments is replaced by the current date, +but without any arguments the string +.Dq Epoch +is printed. +.It +.Sx \&Fl +does not print a dash for an empty argument. +\*[hist] +.It +.Sx \&Fn +does not start a new line unless invoked as the line macro in the +.Em SYNOPSIS +section. +\*[hist] +.It +.Sx \&Fo +with +.Pf non- Sx \&Fa +children causes inconsistent spacing between arguments. +In mandoc, a single space is always inserted between arguments. +.It +.Sx \&Ft +in the +.Em SYNOPSIS +causes inconsistent vertical spacing, depending on whether a prior +.Sx \&Fn +has been invoked. +See +.Sx \&Ft +and +.Sx \&Fn +for the normalised behaviour in mandoc. +.It +.Sx \&In +ignores additional arguments and is not treated specially in the +.Em SYNOPSIS . +\*[hist] +.It +.Sx \&It +sometimes requires a +.Fl nested +flag. +\*[hist] +In new groff and mandoc, any list may be nested by default and +.Fl enum +lists will restart the sequence only for the sub-list. +.It +.Sx \&Li +followed by a delimiter is incorrectly used in some manuals +instead of properly quoting that character, which sometimes works with +historic groff. +.It +.Sx \&Lk +only accepts a single link-name argument; the remainder is misformatted. +.It +.Sx \&Pa +does not format its arguments when used in the FILES section under +certain list types. +.It +.Sx \&Ta +can only be called by other macros, but not at the beginning of a line. +.It +.Sx \&%C +is not implemented. +.It +Historic groff only allows up to eight or nine arguments per macro input +line, depending on the exact situation. +Providing more arguments causes garbled output. +The number of arguments on one input line is not limited with mandoc. +.It +Historic groff has many un-callable macros. +Most of these (excluding some block-level macros) are callable +in new groff and mandoc. +.It +.Sq \(ba +(vertical bar) is not fully supported as a delimiter. +\*[hist] +.It +.Sq \ef +.Pq font face +and +.Sq \ef +.Pq font family face +.Sx Text Decoration +escapes behave irregularly when specified within line-macro scopes. +.It +Negative scaling units return to prior lines. +Instead, mandoc truncates them to zero. +.El +.Pp +The following features are unimplemented in mandoc: +.Pp +.Bl -dash -compact +.It +.Sx \&Bd +.Fl file Ar file . +.It +.Sx \&Bd +.Fl offset Ar center +and +.Fl offset Ar right . +Groff does not implement centred and flush-right rendering either, +but produces large indentations. +.It +The +.Sq \eh +.Pq horizontal position , +.Sq \ev +.Pq vertical position , +.Sq \em +.Pq text colour , +.Sq \eM +.Pq text filling colour , +.Sq \ez +.Pq zero-length character , +.Sq \ew +.Pq string length , +.Sq \ek +.Pq horizontal position marker , +.Sq \eo +.Pq text overstrike , +and +.Sq \es +.Pq text size +escape sequences are all discarded in mandoc. +.It +The +.Sq \ef +scaling unit is accepted by mandoc, but rendered as the default unit. +.It +In quoted literals, groff allows pairwise double-quotes to produce a +standalone double-quote in formatted output. +This is not supported by mandoc. +.El +.Sh SEE ALSO +.Xr man 1 , +.Xr mandoc 1 , +.Xr eqn 7 , +.Xr man 7 , +.Xr mandoc_char 7 , +.Xr roff 7 , +.Xr tbl 7 +.Sh HISTORY +The +.Nm +language first appeared as a troff macro package in +.Bx 4.4 . +It was later significantly updated by Werner Lemberg and Ruslan Ermilov +in groff-1.17. +The standalone implementation that is part of the +.Xr mandoc 1 +utility written by Kristaps Dzonsons appeared in +.Ox 4.6 . +.Sh AUTHORS +The +.Nm +reference was written by +.An Kristaps Dzonsons , +.Mt kristaps@bsd.lv . diff --git a/mdoc.c b/mdoc.c new file mode 100644 index 000000000000..81a4ffc96fd4 --- /dev/null +++ b/mdoc.c @@ -0,0 +1,987 @@ +/* $Id: mdoc.c,v 1.196 2011/09/30 00:13:28 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * Copyright (c) 2010 Ingo Schwarze <schwarze@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/types.h> + +#include <assert.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "mdoc.h" +#include "mandoc.h" +#include "libmdoc.h" +#include "libmandoc.h" + +const char *const __mdoc_macronames[MDOC_MAX] = { + "Ap", "Dd", "Dt", "Os", + "Sh", "Ss", "Pp", "D1", + "Dl", "Bd", "Ed", "Bl", + "El", "It", "Ad", "An", + "Ar", "Cd", "Cm", "Dv", + "Er", "Ev", "Ex", "Fa", + "Fd", "Fl", "Fn", "Ft", + "Ic", "In", "Li", "Nd", + "Nm", "Op", "Ot", "Pa", + "Rv", "St", "Va", "Vt", + /* LINTED */ + "Xr", "%A", "%B", "%D", + /* LINTED */ + "%I", "%J", "%N", "%O", + /* LINTED */ + "%P", "%R", "%T", "%V", + "Ac", "Ao", "Aq", "At", + "Bc", "Bf", "Bo", "Bq", + "Bsx", "Bx", "Db", "Dc", + "Do", "Dq", "Ec", "Ef", + "Em", "Eo", "Fx", "Ms", + "No", "Ns", "Nx", "Ox", + "Pc", "Pf", "Po", "Pq", + "Qc", "Ql", "Qo", "Qq", + "Re", "Rs", "Sc", "So", + "Sq", "Sm", "Sx", "Sy", + "Tn", "Ux", "Xc", "Xo", + "Fo", "Fc", "Oo", "Oc", + "Bk", "Ek", "Bt", "Hf", + "Fr", "Ud", "Lb", "Lp", + "Lk", "Mt", "Brq", "Bro", + /* LINTED */ + "Brc", "%C", "Es", "En", + /* LINTED */ + "Dx", "%Q", "br", "sp", + /* LINTED */ + "%U", "Ta" + }; + +const char *const __mdoc_argnames[MDOC_ARG_MAX] = { + "split", "nosplit", "ragged", + "unfilled", "literal", "file", + "offset", "bullet", "dash", + "hyphen", "item", "enum", + "tag", "diag", "hang", + "ohang", "inset", "column", + "width", "compact", "std", + "filled", "words", "emphasis", + "symbolic", "nested", "centered" + }; + +const char * const *mdoc_macronames = __mdoc_macronames; +const char * const *mdoc_argnames = __mdoc_argnames; + +static void mdoc_node_free(struct mdoc_node *); +static void mdoc_node_unlink(struct mdoc *, + struct mdoc_node *); +static void mdoc_free1(struct mdoc *); +static void mdoc_alloc1(struct mdoc *); +static struct mdoc_node *node_alloc(struct mdoc *, int, int, + enum mdoct, enum mdoc_type); +static int node_append(struct mdoc *, + struct mdoc_node *); +#if 0 +static int mdoc_preptext(struct mdoc *, int, char *, int); +#endif +static int mdoc_ptext(struct mdoc *, int, char *, int); +static int mdoc_pmacro(struct mdoc *, int, char *, int); + +const struct mdoc_node * +mdoc_node(const struct mdoc *m) +{ + + assert( ! (MDOC_HALT & m->flags)); + return(m->first); +} + + +const struct mdoc_meta * +mdoc_meta(const struct mdoc *m) +{ + + assert( ! (MDOC_HALT & m->flags)); + return(&m->meta); +} + + +/* + * Frees volatile resources (parse tree, meta-data, fields). + */ +static void +mdoc_free1(struct mdoc *mdoc) +{ + + if (mdoc->first) + mdoc_node_delete(mdoc, mdoc->first); + if (mdoc->meta.title) + free(mdoc->meta.title); + if (mdoc->meta.os) + free(mdoc->meta.os); + if (mdoc->meta.name) + free(mdoc->meta.name); + if (mdoc->meta.arch) + free(mdoc->meta.arch); + if (mdoc->meta.vol) + free(mdoc->meta.vol); + if (mdoc->meta.msec) + free(mdoc->meta.msec); + if (mdoc->meta.date) + free(mdoc->meta.date); +} + + +/* + * Allocate all volatile resources (parse tree, meta-data, fields). + */ +static void +mdoc_alloc1(struct mdoc *mdoc) +{ + + memset(&mdoc->meta, 0, sizeof(struct mdoc_meta)); + mdoc->flags = 0; + mdoc->lastnamed = mdoc->lastsec = SEC_NONE; + mdoc->last = mandoc_calloc(1, sizeof(struct mdoc_node)); + mdoc->first = mdoc->last; + mdoc->last->type = MDOC_ROOT; + mdoc->last->tok = MDOC_MAX; + mdoc->next = MDOC_NEXT_CHILD; +} + + +/* + * Free up volatile resources (see mdoc_free1()) then re-initialises the + * data with mdoc_alloc1(). After invocation, parse data has been reset + * and the parser is ready for re-invocation on a new tree; however, + * cross-parse non-volatile data is kept intact. + */ +void +mdoc_reset(struct mdoc *mdoc) +{ + + mdoc_free1(mdoc); + mdoc_alloc1(mdoc); +} + + +/* + * Completely free up all volatile and non-volatile parse resources. + * After invocation, the pointer is no longer usable. + */ +void +mdoc_free(struct mdoc *mdoc) +{ + + mdoc_free1(mdoc); + free(mdoc); +} + + +/* + * Allocate volatile and non-volatile parse resources. + */ +struct mdoc * +mdoc_alloc(struct roff *roff, struct mparse *parse) +{ + struct mdoc *p; + + p = mandoc_calloc(1, sizeof(struct mdoc)); + + p->parse = parse; + p->roff = roff; + + mdoc_hash_init(); + mdoc_alloc1(p); + return(p); +} + + +/* + * Climb back up the parse tree, validating open scopes. Mostly calls + * through to macro_end() in macro.c. + */ +int +mdoc_endparse(struct mdoc *m) +{ + + assert( ! (MDOC_HALT & m->flags)); + if (mdoc_macroend(m)) + return(1); + m->flags |= MDOC_HALT; + return(0); +} + +int +mdoc_addeqn(struct mdoc *m, const struct eqn *ep) +{ + struct mdoc_node *n; + + assert( ! (MDOC_HALT & m->flags)); + + /* No text before an initial macro. */ + + if (SEC_NONE == m->lastnamed) { + mdoc_pmsg(m, ep->ln, ep->pos, MANDOCERR_NOTEXT); + return(1); + } + + n = node_alloc(m, ep->ln, ep->pos, MDOC_MAX, MDOC_EQN); + n->eqn = ep; + + if ( ! node_append(m, n)) + return(0); + + m->next = MDOC_NEXT_SIBLING; + return(1); +} + +int +mdoc_addspan(struct mdoc *m, const struct tbl_span *sp) +{ + struct mdoc_node *n; + + assert( ! (MDOC_HALT & m->flags)); + + /* No text before an initial macro. */ + + if (SEC_NONE == m->lastnamed) { + mdoc_pmsg(m, sp->line, 0, MANDOCERR_NOTEXT); + return(1); + } + + n = node_alloc(m, sp->line, 0, MDOC_MAX, MDOC_TBL); + n->span = sp; + + if ( ! node_append(m, n)) + return(0); + + m->next = MDOC_NEXT_SIBLING; + return(1); +} + + +/* + * Main parse routine. Parses a single line -- really just hands off to + * the macro (mdoc_pmacro()) or text parser (mdoc_ptext()). + */ +int +mdoc_parseln(struct mdoc *m, int ln, char *buf, int offs) +{ + + assert( ! (MDOC_HALT & m->flags)); + + m->flags |= MDOC_NEWLINE; + + /* + * Let the roff nS register switch SYNOPSIS mode early, + * such that the parser knows at all times + * whether this mode is on or off. + * Note that this mode is also switched by the Sh macro. + */ + if (roff_regisset(m->roff, REG_nS)) { + if (roff_regget(m->roff, REG_nS)) + m->flags |= MDOC_SYNOPSIS; + else + m->flags &= ~MDOC_SYNOPSIS; + } + + return(mandoc_getcontrol(buf, &offs) ? + mdoc_pmacro(m, ln, buf, offs) : + mdoc_ptext(m, ln, buf, offs)); +} + +int +mdoc_macro(MACRO_PROT_ARGS) +{ + assert(tok < MDOC_MAX); + + /* If we're in the body, deny prologue calls. */ + + if (MDOC_PROLOGUE & mdoc_macros[tok].flags && + MDOC_PBODY & m->flags) { + mdoc_pmsg(m, line, ppos, MANDOCERR_BADBODY); + return(1); + } + + /* If we're in the prologue, deny "body" macros. */ + + if ( ! (MDOC_PROLOGUE & mdoc_macros[tok].flags) && + ! (MDOC_PBODY & m->flags)) { + mdoc_pmsg(m, line, ppos, MANDOCERR_BADPROLOG); + if (NULL == m->meta.msec) + m->meta.msec = mandoc_strdup("1"); + if (NULL == m->meta.title) + m->meta.title = mandoc_strdup("UNKNOWN"); + if (NULL == m->meta.vol) + m->meta.vol = mandoc_strdup("LOCAL"); + if (NULL == m->meta.os) + m->meta.os = mandoc_strdup("LOCAL"); + if (NULL == m->meta.date) + m->meta.date = mandoc_normdate + (m->parse, NULL, line, ppos); + m->flags |= MDOC_PBODY; + } + + return((*mdoc_macros[tok].fp)(m, tok, line, ppos, pos, buf)); +} + + +static int +node_append(struct mdoc *mdoc, struct mdoc_node *p) +{ + + assert(mdoc->last); + assert(mdoc->first); + assert(MDOC_ROOT != p->type); + + switch (mdoc->next) { + case (MDOC_NEXT_SIBLING): + mdoc->last->next = p; + p->prev = mdoc->last; + p->parent = mdoc->last->parent; + break; + case (MDOC_NEXT_CHILD): + mdoc->last->child = p; + p->parent = mdoc->last; + break; + default: + abort(); + /* NOTREACHED */ + } + + p->parent->nchild++; + + /* + * Copy over the normalised-data pointer of our parent. Not + * everybody has one, but copying a null pointer is fine. + */ + + switch (p->type) { + case (MDOC_BODY): + /* FALLTHROUGH */ + case (MDOC_TAIL): + /* FALLTHROUGH */ + case (MDOC_HEAD): + p->norm = p->parent->norm; + break; + default: + break; + } + + if ( ! mdoc_valid_pre(mdoc, p)) + return(0); + + switch (p->type) { + case (MDOC_HEAD): + assert(MDOC_BLOCK == p->parent->type); + p->parent->head = p; + break; + case (MDOC_TAIL): + assert(MDOC_BLOCK == p->parent->type); + p->parent->tail = p; + break; + case (MDOC_BODY): + if (p->end) + break; + assert(MDOC_BLOCK == p->parent->type); + p->parent->body = p; + break; + default: + break; + } + + mdoc->last = p; + + switch (p->type) { + case (MDOC_TBL): + /* FALLTHROUGH */ + case (MDOC_TEXT): + if ( ! mdoc_valid_post(mdoc)) + return(0); + break; + default: + break; + } + + return(1); +} + + +static struct mdoc_node * +node_alloc(struct mdoc *m, int line, int pos, + enum mdoct tok, enum mdoc_type type) +{ + struct mdoc_node *p; + + p = mandoc_calloc(1, sizeof(struct mdoc_node)); + p->sec = m->lastsec; + p->line = line; + p->pos = pos; + p->tok = tok; + p->type = type; + + /* Flag analysis. */ + + if (MDOC_SYNOPSIS & m->flags) + p->flags |= MDOC_SYNPRETTY; + else + p->flags &= ~MDOC_SYNPRETTY; + if (MDOC_NEWLINE & m->flags) + p->flags |= MDOC_LINE; + m->flags &= ~MDOC_NEWLINE; + + return(p); +} + + +int +mdoc_tail_alloc(struct mdoc *m, int line, int pos, enum mdoct tok) +{ + struct mdoc_node *p; + + p = node_alloc(m, line, pos, tok, MDOC_TAIL); + if ( ! node_append(m, p)) + return(0); + m->next = MDOC_NEXT_CHILD; + return(1); +} + + +int +mdoc_head_alloc(struct mdoc *m, int line, int pos, enum mdoct tok) +{ + struct mdoc_node *p; + + assert(m->first); + assert(m->last); + + p = node_alloc(m, line, pos, tok, MDOC_HEAD); + if ( ! node_append(m, p)) + return(0); + m->next = MDOC_NEXT_CHILD; + return(1); +} + + +int +mdoc_body_alloc(struct mdoc *m, int line, int pos, enum mdoct tok) +{ + struct mdoc_node *p; + + p = node_alloc(m, line, pos, tok, MDOC_BODY); + if ( ! node_append(m, p)) + return(0); + m->next = MDOC_NEXT_CHILD; + return(1); +} + + +int +mdoc_endbody_alloc(struct mdoc *m, int line, int pos, enum mdoct tok, + struct mdoc_node *body, enum mdoc_endbody end) +{ + struct mdoc_node *p; + + p = node_alloc(m, line, pos, tok, MDOC_BODY); + p->pending = body; + p->end = end; + if ( ! node_append(m, p)) + return(0); + m->next = MDOC_NEXT_SIBLING; + return(1); +} + + +int +mdoc_block_alloc(struct mdoc *m, int line, int pos, + enum mdoct tok, struct mdoc_arg *args) +{ + struct mdoc_node *p; + + p = node_alloc(m, line, pos, tok, MDOC_BLOCK); + p->args = args; + if (p->args) + (args->refcnt)++; + + switch (tok) { + case (MDOC_Bd): + /* FALLTHROUGH */ + case (MDOC_Bf): + /* FALLTHROUGH */ + case (MDOC_Bl): + /* FALLTHROUGH */ + case (MDOC_Rs): + p->norm = mandoc_calloc(1, sizeof(union mdoc_data)); + break; + default: + break; + } + + if ( ! node_append(m, p)) + return(0); + m->next = MDOC_NEXT_CHILD; + return(1); +} + + +int +mdoc_elem_alloc(struct mdoc *m, int line, int pos, + enum mdoct tok, struct mdoc_arg *args) +{ + struct mdoc_node *p; + + p = node_alloc(m, line, pos, tok, MDOC_ELEM); + p->args = args; + if (p->args) + (args->refcnt)++; + + switch (tok) { + case (MDOC_An): + p->norm = mandoc_calloc(1, sizeof(union mdoc_data)); + break; + default: + break; + } + + if ( ! node_append(m, p)) + return(0); + m->next = MDOC_NEXT_CHILD; + return(1); +} + +int +mdoc_word_alloc(struct mdoc *m, int line, int pos, const char *p) +{ + struct mdoc_node *n; + + n = node_alloc(m, line, pos, MDOC_MAX, MDOC_TEXT); + n->string = roff_strdup(m->roff, p); + + if ( ! node_append(m, n)) + return(0); + + m->next = MDOC_NEXT_SIBLING; + return(1); +} + + +static void +mdoc_node_free(struct mdoc_node *p) +{ + + if (MDOC_BLOCK == p->type || MDOC_ELEM == p->type) + free(p->norm); + if (p->string) + free(p->string); + if (p->args) + mdoc_argv_free(p->args); + free(p); +} + + +static void +mdoc_node_unlink(struct mdoc *m, struct mdoc_node *n) +{ + + /* Adjust siblings. */ + + if (n->prev) + n->prev->next = n->next; + if (n->next) + n->next->prev = n->prev; + + /* Adjust parent. */ + + if (n->parent) { + n->parent->nchild--; + if (n->parent->child == n) + n->parent->child = n->prev ? n->prev : n->next; + if (n->parent->last == n) + n->parent->last = n->prev ? n->prev : NULL; + } + + /* Adjust parse point, if applicable. */ + + if (m && m->last == n) { + if (n->prev) { + m->last = n->prev; + m->next = MDOC_NEXT_SIBLING; + } else { + m->last = n->parent; + m->next = MDOC_NEXT_CHILD; + } + } + + if (m && m->first == n) + m->first = NULL; +} + + +void +mdoc_node_delete(struct mdoc *m, struct mdoc_node *p) +{ + + while (p->child) { + assert(p->nchild); + mdoc_node_delete(m, p->child); + } + assert(0 == p->nchild); + + mdoc_node_unlink(m, p); + mdoc_node_free(p); +} + +#if 0 +/* + * Pre-treat a text line. + * Text lines can consist of equations, which must be handled apart from + * the regular text. + * Thus, use this function to step through a line checking if it has any + * equations embedded in it. + * This must handle multiple equations AND equations that do not end at + * the end-of-line, i.e., will re-enter in the next roff parse. + */ +static int +mdoc_preptext(struct mdoc *m, int line, char *buf, int offs) +{ + char *start, *end; + char delim; + + while ('\0' != buf[offs]) { + /* Mark starting position if eqn is set. */ + start = NULL; + if ('\0' != (delim = roff_eqndelim(m->roff))) + if (NULL != (start = strchr(buf + offs, delim))) + *start++ = '\0'; + + /* Parse text as normal. */ + if ( ! mdoc_ptext(m, line, buf, offs)) + return(0); + + /* Continue only if an equation exists. */ + if (NULL == start) + break; + + /* Read past the end of the equation. */ + offs += start - (buf + offs); + assert(start == &buf[offs]); + if (NULL != (end = strchr(buf + offs, delim))) { + *end++ = '\0'; + while (' ' == *end) + end++; + } + + /* Parse the equation itself. */ + roff_openeqn(m->roff, NULL, line, offs, buf); + + /* Process a finished equation? */ + if (roff_closeeqn(m->roff)) + if ( ! mdoc_addeqn(m, roff_eqn(m->roff))) + return(0); + offs += (end - (buf + offs)); + } + + return(1); +} +#endif + +/* + * Parse free-form text, that is, a line that does not begin with the + * control character. + */ +static int +mdoc_ptext(struct mdoc *m, int line, char *buf, int offs) +{ + char *c, *ws, *end; + struct mdoc_node *n; + + /* No text before an initial macro. */ + + if (SEC_NONE == m->lastnamed) { + mdoc_pmsg(m, line, offs, MANDOCERR_NOTEXT); + return(1); + } + + assert(m->last); + n = m->last; + + /* + * Divert directly to list processing if we're encountering a + * columnar MDOC_BLOCK with or without a prior MDOC_BLOCK entry + * (a MDOC_BODY means it's already open, in which case we should + * process within its context in the normal way). + */ + + if (MDOC_Bl == n->tok && MDOC_BODY == n->type && + LIST_column == n->norm->Bl.type) { + /* `Bl' is open without any children. */ + m->flags |= MDOC_FREECOL; + return(mdoc_macro(m, MDOC_It, line, offs, &offs, buf)); + } + + if (MDOC_It == n->tok && MDOC_BLOCK == n->type && + NULL != n->parent && + MDOC_Bl == n->parent->tok && + LIST_column == n->parent->norm->Bl.type) { + /* `Bl' has block-level `It' children. */ + m->flags |= MDOC_FREECOL; + return(mdoc_macro(m, MDOC_It, line, offs, &offs, buf)); + } + + /* + * Search for the beginning of unescaped trailing whitespace (ws) + * and for the first character not to be output (end). + */ + + /* FIXME: replace with strcspn(). */ + ws = NULL; + for (c = end = buf + offs; *c; c++) { + switch (*c) { + case ' ': + if (NULL == ws) + ws = c; + continue; + case '\t': + /* + * Always warn about trailing tabs, + * even outside literal context, + * where they should be put on the next line. + */ + if (NULL == ws) + ws = c; + /* + * Strip trailing tabs in literal context only; + * outside, they affect the next line. + */ + if (MDOC_LITERAL & m->flags) + continue; + break; + case '\\': + /* Skip the escaped character, too, if any. */ + if (c[1]) + c++; + /* FALLTHROUGH */ + default: + ws = NULL; + break; + } + end = c + 1; + } + *end = '\0'; + + if (ws) + mdoc_pmsg(m, line, (int)(ws-buf), MANDOCERR_EOLNSPACE); + + if ('\0' == buf[offs] && ! (MDOC_LITERAL & m->flags)) { + mdoc_pmsg(m, line, (int)(c-buf), MANDOCERR_NOBLANKLN); + + /* + * Insert a `sp' in the case of a blank line. Technically, + * blank lines aren't allowed, but enough manuals assume this + * behaviour that we want to work around it. + */ + if ( ! mdoc_elem_alloc(m, line, offs, MDOC_sp, NULL)) + return(0); + + m->next = MDOC_NEXT_SIBLING; + return(1); + } + + if ( ! mdoc_word_alloc(m, line, offs, buf+offs)) + return(0); + + if (MDOC_LITERAL & m->flags) + return(1); + + /* + * End-of-sentence check. If the last character is an unescaped + * EOS character, then flag the node as being the end of a + * sentence. The front-end will know how to interpret this. + */ + + assert(buf < end); + + if (mandoc_eos(buf+offs, (size_t)(end-buf-offs), 0)) + m->last->flags |= MDOC_EOS; + + return(1); +} + + +/* + * Parse a macro line, that is, a line beginning with the control + * character. + */ +static int +mdoc_pmacro(struct mdoc *m, int ln, char *buf, int offs) +{ + enum mdoct tok; + int i, sv; + char mac[5]; + struct mdoc_node *n; + + /* Empty post-control lines are ignored. */ + + if ('"' == buf[offs]) { + mdoc_pmsg(m, ln, offs, MANDOCERR_BADCOMMENT); + return(1); + } else if ('\0' == buf[offs]) + return(1); + + sv = offs; + + /* + * Copy the first word into a nil-terminated buffer. + * Stop copying when a tab, space, or eoln is encountered. + */ + + i = 0; + while (i < 4 && '\0' != buf[offs] && + ' ' != buf[offs] && '\t' != buf[offs]) + mac[i++] = buf[offs++]; + + mac[i] = '\0'; + + tok = (i > 1 || i < 4) ? mdoc_hash_find(mac) : MDOC_MAX; + + if (MDOC_MAX == tok) { + mandoc_vmsg(MANDOCERR_MACRO, m->parse, + ln, sv, "%s", buf + sv - 1); + return(1); + } + + /* Disregard the first trailing tab, if applicable. */ + + if ('\t' == buf[offs]) + offs++; + + /* Jump to the next non-whitespace word. */ + + while (buf[offs] && ' ' == buf[offs]) + offs++; + + /* + * Trailing whitespace. Note that tabs are allowed to be passed + * into the parser as "text", so we only warn about spaces here. + */ + + if ('\0' == buf[offs] && ' ' == buf[offs - 1]) + mdoc_pmsg(m, ln, offs - 1, MANDOCERR_EOLNSPACE); + + /* + * If an initial macro or a list invocation, divert directly + * into macro processing. + */ + + if (NULL == m->last || MDOC_It == tok || MDOC_El == tok) { + if ( ! mdoc_macro(m, tok, ln, sv, &offs, buf)) + goto err; + return(1); + } + + n = m->last; + assert(m->last); + + /* + * If the first macro of a `Bl -column', open an `It' block + * context around the parsed macro. + */ + + if (MDOC_Bl == n->tok && MDOC_BODY == n->type && + LIST_column == n->norm->Bl.type) { + m->flags |= MDOC_FREECOL; + if ( ! mdoc_macro(m, MDOC_It, ln, sv, &sv, buf)) + goto err; + return(1); + } + + /* + * If we're following a block-level `It' within a `Bl -column' + * context (perhaps opened in the above block or in ptext()), + * then open an `It' block context around the parsed macro. + */ + + if (MDOC_It == n->tok && MDOC_BLOCK == n->type && + NULL != n->parent && + MDOC_Bl == n->parent->tok && + LIST_column == n->parent->norm->Bl.type) { + m->flags |= MDOC_FREECOL; + if ( ! mdoc_macro(m, MDOC_It, ln, sv, &sv, buf)) + goto err; + return(1); + } + + /* Normal processing of a macro. */ + + if ( ! mdoc_macro(m, tok, ln, sv, &offs, buf)) + goto err; + + return(1); + +err: /* Error out. */ + + m->flags |= MDOC_HALT; + return(0); +} + +enum mdelim +mdoc_isdelim(const char *p) +{ + + if ('\0' == p[0]) + return(DELIM_NONE); + + if ('\0' == p[1]) + switch (p[0]) { + case('('): + /* FALLTHROUGH */ + case('['): + return(DELIM_OPEN); + case('|'): + return(DELIM_MIDDLE); + case('.'): + /* FALLTHROUGH */ + case(','): + /* FALLTHROUGH */ + case(';'): + /* FALLTHROUGH */ + case(':'): + /* FALLTHROUGH */ + case('?'): + /* FALLTHROUGH */ + case('!'): + /* FALLTHROUGH */ + case(')'): + /* FALLTHROUGH */ + case(']'): + return(DELIM_CLOSE); + default: + return(DELIM_NONE); + } + + if ('\\' != p[0]) + return(DELIM_NONE); + + if (0 == strcmp(p + 1, ".")) + return(DELIM_CLOSE); + if (0 == strcmp(p + 1, "*(Ba")) + return(DELIM_MIDDLE); + + return(DELIM_NONE); +} diff --git a/mdoc.h b/mdoc.h new file mode 100644 index 000000000000..9cee098e7fee --- /dev/null +++ b/mdoc.h @@ -0,0 +1,392 @@ +/* $Id: mdoc.h,v 1.122 2011/03/22 14:05:45 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef MDOC_H +#define MDOC_H + +enum mdoct { + MDOC_Ap = 0, + MDOC_Dd, + MDOC_Dt, + MDOC_Os, + MDOC_Sh, + MDOC_Ss, + MDOC_Pp, + MDOC_D1, + MDOC_Dl, + MDOC_Bd, + MDOC_Ed, + MDOC_Bl, + MDOC_El, + MDOC_It, + MDOC_Ad, + MDOC_An, + MDOC_Ar, + MDOC_Cd, + MDOC_Cm, + MDOC_Dv, + MDOC_Er, + MDOC_Ev, + MDOC_Ex, + MDOC_Fa, + MDOC_Fd, + MDOC_Fl, + MDOC_Fn, + MDOC_Ft, + MDOC_Ic, + MDOC_In, + MDOC_Li, + MDOC_Nd, + MDOC_Nm, + MDOC_Op, + MDOC_Ot, + MDOC_Pa, + MDOC_Rv, + MDOC_St, + MDOC_Va, + MDOC_Vt, + MDOC_Xr, + MDOC__A, + MDOC__B, + MDOC__D, + MDOC__I, + MDOC__J, + MDOC__N, + MDOC__O, + MDOC__P, + MDOC__R, + MDOC__T, + MDOC__V, + MDOC_Ac, + MDOC_Ao, + MDOC_Aq, + MDOC_At, + MDOC_Bc, + MDOC_Bf, + MDOC_Bo, + MDOC_Bq, + MDOC_Bsx, + MDOC_Bx, + MDOC_Db, + MDOC_Dc, + MDOC_Do, + MDOC_Dq, + MDOC_Ec, + MDOC_Ef, + MDOC_Em, + MDOC_Eo, + MDOC_Fx, + MDOC_Ms, + MDOC_No, + MDOC_Ns, + MDOC_Nx, + MDOC_Ox, + MDOC_Pc, + MDOC_Pf, + MDOC_Po, + MDOC_Pq, + MDOC_Qc, + MDOC_Ql, + MDOC_Qo, + MDOC_Qq, + MDOC_Re, + MDOC_Rs, + MDOC_Sc, + MDOC_So, + MDOC_Sq, + MDOC_Sm, + MDOC_Sx, + MDOC_Sy, + MDOC_Tn, + MDOC_Ux, + MDOC_Xc, + MDOC_Xo, + MDOC_Fo, + MDOC_Fc, + MDOC_Oo, + MDOC_Oc, + MDOC_Bk, + MDOC_Ek, + MDOC_Bt, + MDOC_Hf, + MDOC_Fr, + MDOC_Ud, + MDOC_Lb, + MDOC_Lp, + MDOC_Lk, + MDOC_Mt, + MDOC_Brq, + MDOC_Bro, + MDOC_Brc, + MDOC__C, + MDOC_Es, + MDOC_En, + MDOC_Dx, + MDOC__Q, + MDOC_br, + MDOC_sp, + MDOC__U, + MDOC_Ta, + MDOC_MAX +}; + +enum mdocargt { + MDOC_Split, /* -split */ + MDOC_Nosplit, /* -nospli */ + MDOC_Ragged, /* -ragged */ + MDOC_Unfilled, /* -unfilled */ + MDOC_Literal, /* -literal */ + MDOC_File, /* -file */ + MDOC_Offset, /* -offset */ + MDOC_Bullet, /* -bullet */ + MDOC_Dash, /* -dash */ + MDOC_Hyphen, /* -hyphen */ + MDOC_Item, /* -item */ + MDOC_Enum, /* -enum */ + MDOC_Tag, /* -tag */ + MDOC_Diag, /* -diag */ + MDOC_Hang, /* -hang */ + MDOC_Ohang, /* -ohang */ + MDOC_Inset, /* -inset */ + MDOC_Column, /* -column */ + MDOC_Width, /* -width */ + MDOC_Compact, /* -compact */ + MDOC_Std, /* -std */ + MDOC_Filled, /* -filled */ + MDOC_Words, /* -words */ + MDOC_Emphasis, /* -emphasis */ + MDOC_Symbolic, /* -symbolic */ + MDOC_Nested, /* -nested */ + MDOC_Centred, /* -centered */ + MDOC_ARG_MAX +}; + +enum mdoc_type { + MDOC_TEXT, + MDOC_ELEM, + MDOC_HEAD, + MDOC_TAIL, + MDOC_BODY, + MDOC_BLOCK, + MDOC_TBL, + MDOC_EQN, + MDOC_ROOT +}; + +/* + * Section (named/unnamed) of `Sh'. Note that these appear in the + * conventional order imposed by mdoc.7. In the case of SEC_NONE, no + * section has been invoked (this shouldn't happen). SEC_CUSTOM refers + * to other sections. + */ +enum mdoc_sec { + SEC_NONE = 0, + SEC_NAME, /* NAME */ + SEC_LIBRARY, /* LIBRARY */ + SEC_SYNOPSIS, /* SYNOPSIS */ + SEC_DESCRIPTION, /* DESCRIPTION */ + SEC_IMPLEMENTATION, /* IMPLEMENTATION NOTES */ + SEC_RETURN_VALUES, /* RETURN VALUES */ + SEC_ENVIRONMENT, /* ENVIRONMENT */ + SEC_FILES, /* FILES */ + SEC_EXIT_STATUS, /* EXIT STATUS */ + SEC_EXAMPLES, /* EXAMPLES */ + SEC_DIAGNOSTICS, /* DIAGNOSTICS */ + SEC_COMPATIBILITY, /* COMPATIBILITY */ + SEC_ERRORS, /* ERRORS */ + SEC_SEE_ALSO, /* SEE ALSO */ + SEC_STANDARDS, /* STANDARDS */ + SEC_HISTORY, /* HISTORY */ + SEC_AUTHORS, /* AUTHORS */ + SEC_CAVEATS, /* CAVEATS */ + SEC_BUGS, /* BUGS */ + SEC_SECURITY, /* SECURITY */ + SEC_CUSTOM, + SEC__MAX +}; + +struct mdoc_meta { + char *msec; /* `Dt' section (1, 3p, etc.) */ + char *vol; /* `Dt' volume (implied) */ + char *arch; /* `Dt' arch (i386, etc.) */ + char *date; /* `Dd' normalised date */ + char *title; /* `Dt' title (FOO, etc.) */ + char *os; /* `Os' system (OpenBSD, etc.) */ + char *name; /* leading `Nm' name */ +}; + +/* + * An argument to a macro (multiple values = `-column xxx yyy'). + */ +struct mdoc_argv { + enum mdocargt arg; /* type of argument */ + int line; + int pos; + size_t sz; /* elements in "value" */ + char **value; /* argument strings */ +}; + +/* + * Reference-counted macro arguments. These are refcounted because + * blocks have multiple instances of the same arguments spread across + * the HEAD, BODY, TAIL, and BLOCK node types. + */ +struct mdoc_arg { + size_t argc; + struct mdoc_argv *argv; + unsigned int refcnt; +}; + +/* + * Indicates that a BODY's formatting has ended, but the scope is still + * open. Used for syntax-broken blocks. + */ +enum mdoc_endbody { + ENDBODY_NOT = 0, + ENDBODY_SPACE, /* is broken: append a space */ + ENDBODY_NOSPACE /* is broken: don't append a space */ +}; + +enum mdoc_list { + LIST__NONE = 0, + LIST_bullet, /* -bullet */ + LIST_column, /* -column */ + LIST_dash, /* -dash */ + LIST_diag, /* -diag */ + LIST_enum, /* -enum */ + LIST_hang, /* -hang */ + LIST_hyphen, /* -hyphen */ + LIST_inset, /* -inset */ + LIST_item, /* -item */ + LIST_ohang, /* -ohang */ + LIST_tag, /* -tag */ + LIST_MAX +}; + +enum mdoc_disp { + DISP__NONE = 0, + DISP_centred, /* -centered */ + DISP_ragged, /* -ragged */ + DISP_unfilled, /* -unfilled */ + DISP_filled, /* -filled */ + DISP_literal /* -literal */ +}; + +enum mdoc_auth { + AUTH__NONE = 0, + AUTH_split, /* -split */ + AUTH_nosplit /* -nosplit */ +}; + +enum mdoc_font { + FONT__NONE = 0, + FONT_Em, /* Em, -emphasis */ + FONT_Li, /* Li, -literal */ + FONT_Sy /* Sy, -symbolic */ +}; + +struct mdoc_bd { + const char *offs; /* -offset */ + enum mdoc_disp type; /* -ragged, etc. */ + int comp; /* -compact */ +}; + +struct mdoc_bl { + const char *width; /* -width */ + const char *offs; /* -offset */ + enum mdoc_list type; /* -tag, -enum, etc. */ + int comp; /* -compact */ + size_t ncols; /* -column arg count */ + const char **cols; /* -column val ptr */ +}; + +struct mdoc_bf { + enum mdoc_font font; /* font */ +}; + +struct mdoc_an { + enum mdoc_auth auth; /* -split, etc. */ +}; + +struct mdoc_rs { + int quote_T; /* whether to quote %T */ +}; + +/* + * Consists of normalised node arguments. These should be used instead + * of iterating through the mdoc_arg pointers of a node: defaults are + * provided, etc. + */ +union mdoc_data { + struct mdoc_an An; + struct mdoc_bd Bd; + struct mdoc_bf Bf; + struct mdoc_bl Bl; + struct mdoc_rs Rs; +}; + +/* + * Single node in tree-linked AST. + */ +struct mdoc_node { + struct mdoc_node *parent; /* parent AST node */ + struct mdoc_node *child; /* first child AST node */ + struct mdoc_node *last; /* last child AST node */ + struct mdoc_node *next; /* sibling AST node */ + struct mdoc_node *prev; /* prior sibling AST node */ + int nchild; /* number children */ + int line; /* parse line */ + int pos; /* parse column */ + enum mdoct tok; /* tok or MDOC__MAX if none */ + int flags; +#define MDOC_VALID (1 << 0) /* has been validated */ +#define MDOC_EOS (1 << 2) /* at sentence boundary */ +#define MDOC_LINE (1 << 3) /* first macro/text on line */ +#define MDOC_SYNPRETTY (1 << 4) /* SYNOPSIS-style formatting */ +#define MDOC_ENDED (1 << 5) /* rendering has been ended */ +#define MDOC_DELIMO (1 << 6) +#define MDOC_DELIMC (1 << 7) + enum mdoc_type type; /* AST node type */ + enum mdoc_sec sec; /* current named section */ + union mdoc_data *norm; /* normalised args */ + /* FIXME: these can be union'd to shave a few bytes. */ + struct mdoc_arg *args; /* BLOCK/ELEM */ + struct mdoc_node *pending; /* BLOCK */ + struct mdoc_node *head; /* BLOCK */ + struct mdoc_node *body; /* BLOCK */ + struct mdoc_node *tail; /* BLOCK */ + char *string; /* TEXT */ + const struct tbl_span *span; /* TBL */ + const struct eqn *eqn; /* EQN */ + enum mdoc_endbody end; /* BODY */ +}; + +/* Names of macros. Index is enum mdoct. */ +extern const char *const *mdoc_macronames; + +/* Names of macro args. Index is enum mdocargt. */ +extern const char *const *mdoc_argnames; + +__BEGIN_DECLS + +struct mdoc; + +const struct mdoc_node *mdoc_node(const struct mdoc *); +const struct mdoc_meta *mdoc_meta(const struct mdoc *); + +__END_DECLS + +#endif /*!MDOC_H*/ diff --git a/mdoc_argv.c b/mdoc_argv.c new file mode 100644 index 000000000000..08386e09b14e --- /dev/null +++ b/mdoc_argv.c @@ -0,0 +1,716 @@ +/* $Id: mdoc_argv.c,v 1.82 2012/03/23 05:50:24 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/types.h> + +#include <assert.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include "mdoc.h" +#include "mandoc.h" +#include "libmdoc.h" +#include "libmandoc.h" + +#define MULTI_STEP 5 /* pre-allocate argument values */ +#define DELIMSZ 6 /* max possible size of a delimiter */ + +enum argsflag { + ARGSFL_NONE = 0, + ARGSFL_DELIM, /* handle delimiters of [[::delim::][ ]+]+ */ + ARGSFL_TABSEP /* handle tab/`Ta' separated phrases */ +}; + +enum argvflag { + ARGV_NONE, /* no args to flag (e.g., -split) */ + ARGV_SINGLE, /* one arg to flag (e.g., -file xxx) */ + ARGV_MULTI, /* multiple args (e.g., -column xxx yyy) */ + ARGV_OPT_SINGLE /* optional arg (e.g., -offset [xxx]) */ +}; + +struct mdocarg { + enum argsflag flags; + const enum mdocargt *argvs; +}; + +static void argn_free(struct mdoc_arg *, int); +static enum margserr args(struct mdoc *, int, int *, + char *, enum argsflag, char **); +static int args_checkpunct(const char *, int); +static int argv_multi(struct mdoc *, int, + struct mdoc_argv *, int *, char *); +static int argv_opt_single(struct mdoc *, int, + struct mdoc_argv *, int *, char *); +static int argv_single(struct mdoc *, int, + struct mdoc_argv *, int *, char *); + +static const enum argvflag argvflags[MDOC_ARG_MAX] = { + ARGV_NONE, /* MDOC_Split */ + ARGV_NONE, /* MDOC_Nosplit */ + ARGV_NONE, /* MDOC_Ragged */ + ARGV_NONE, /* MDOC_Unfilled */ + ARGV_NONE, /* MDOC_Literal */ + ARGV_SINGLE, /* MDOC_File */ + ARGV_OPT_SINGLE, /* MDOC_Offset */ + ARGV_NONE, /* MDOC_Bullet */ + ARGV_NONE, /* MDOC_Dash */ + ARGV_NONE, /* MDOC_Hyphen */ + ARGV_NONE, /* MDOC_Item */ + ARGV_NONE, /* MDOC_Enum */ + ARGV_NONE, /* MDOC_Tag */ + ARGV_NONE, /* MDOC_Diag */ + ARGV_NONE, /* MDOC_Hang */ + ARGV_NONE, /* MDOC_Ohang */ + ARGV_NONE, /* MDOC_Inset */ + ARGV_MULTI, /* MDOC_Column */ + ARGV_OPT_SINGLE, /* MDOC_Width */ + ARGV_NONE, /* MDOC_Compact */ + ARGV_NONE, /* MDOC_Std */ + ARGV_NONE, /* MDOC_Filled */ + ARGV_NONE, /* MDOC_Words */ + ARGV_NONE, /* MDOC_Emphasis */ + ARGV_NONE, /* MDOC_Symbolic */ + ARGV_NONE /* MDOC_Symbolic */ +}; + +static const enum mdocargt args_Ex[] = { + MDOC_Std, + MDOC_ARG_MAX +}; + +static const enum mdocargt args_An[] = { + MDOC_Split, + MDOC_Nosplit, + MDOC_ARG_MAX +}; + +static const enum mdocargt args_Bd[] = { + MDOC_Ragged, + MDOC_Unfilled, + MDOC_Filled, + MDOC_Literal, + MDOC_File, + MDOC_Offset, + MDOC_Compact, + MDOC_Centred, + MDOC_ARG_MAX +}; + +static const enum mdocargt args_Bf[] = { + MDOC_Emphasis, + MDOC_Literal, + MDOC_Symbolic, + MDOC_ARG_MAX +}; + +static const enum mdocargt args_Bk[] = { + MDOC_Words, + MDOC_ARG_MAX +}; + +static const enum mdocargt args_Bl[] = { + MDOC_Bullet, + MDOC_Dash, + MDOC_Hyphen, + MDOC_Item, + MDOC_Enum, + MDOC_Tag, + MDOC_Diag, + MDOC_Hang, + MDOC_Ohang, + MDOC_Inset, + MDOC_Column, + MDOC_Width, + MDOC_Offset, + MDOC_Compact, + MDOC_Nested, + MDOC_ARG_MAX +}; + +static const struct mdocarg mdocargs[MDOC_MAX] = { + { ARGSFL_NONE, NULL }, /* Ap */ + { ARGSFL_NONE, NULL }, /* Dd */ + { ARGSFL_NONE, NULL }, /* Dt */ + { ARGSFL_NONE, NULL }, /* Os */ + { ARGSFL_NONE, NULL }, /* Sh */ + { ARGSFL_NONE, NULL }, /* Ss */ + { ARGSFL_NONE, NULL }, /* Pp */ + { ARGSFL_DELIM, NULL }, /* D1 */ + { ARGSFL_DELIM, NULL }, /* Dl */ + { ARGSFL_NONE, args_Bd }, /* Bd */ + { ARGSFL_NONE, NULL }, /* Ed */ + { ARGSFL_NONE, args_Bl }, /* Bl */ + { ARGSFL_NONE, NULL }, /* El */ + { ARGSFL_NONE, NULL }, /* It */ + { ARGSFL_DELIM, NULL }, /* Ad */ + { ARGSFL_DELIM, args_An }, /* An */ + { ARGSFL_DELIM, NULL }, /* Ar */ + { ARGSFL_NONE, NULL }, /* Cd */ + { ARGSFL_DELIM, NULL }, /* Cm */ + { ARGSFL_DELIM, NULL }, /* Dv */ + { ARGSFL_DELIM, NULL }, /* Er */ + { ARGSFL_DELIM, NULL }, /* Ev */ + { ARGSFL_NONE, args_Ex }, /* Ex */ + { ARGSFL_DELIM, NULL }, /* Fa */ + { ARGSFL_NONE, NULL }, /* Fd */ + { ARGSFL_DELIM, NULL }, /* Fl */ + { ARGSFL_DELIM, NULL }, /* Fn */ + { ARGSFL_DELIM, NULL }, /* Ft */ + { ARGSFL_DELIM, NULL }, /* Ic */ + { ARGSFL_NONE, NULL }, /* In */ + { ARGSFL_DELIM, NULL }, /* Li */ + { ARGSFL_NONE, NULL }, /* Nd */ + { ARGSFL_DELIM, NULL }, /* Nm */ + { ARGSFL_DELIM, NULL }, /* Op */ + { ARGSFL_NONE, NULL }, /* Ot */ + { ARGSFL_DELIM, NULL }, /* Pa */ + { ARGSFL_NONE, args_Ex }, /* Rv */ + { ARGSFL_DELIM, NULL }, /* St */ + { ARGSFL_DELIM, NULL }, /* Va */ + { ARGSFL_DELIM, NULL }, /* Vt */ + { ARGSFL_DELIM, NULL }, /* Xr */ + { ARGSFL_NONE, NULL }, /* %A */ + { ARGSFL_NONE, NULL }, /* %B */ + { ARGSFL_NONE, NULL }, /* %D */ + { ARGSFL_NONE, NULL }, /* %I */ + { ARGSFL_NONE, NULL }, /* %J */ + { ARGSFL_NONE, NULL }, /* %N */ + { ARGSFL_NONE, NULL }, /* %O */ + { ARGSFL_NONE, NULL }, /* %P */ + { ARGSFL_NONE, NULL }, /* %R */ + { ARGSFL_NONE, NULL }, /* %T */ + { ARGSFL_NONE, NULL }, /* %V */ + { ARGSFL_DELIM, NULL }, /* Ac */ + { ARGSFL_NONE, NULL }, /* Ao */ + { ARGSFL_DELIM, NULL }, /* Aq */ + { ARGSFL_DELIM, NULL }, /* At */ + { ARGSFL_DELIM, NULL }, /* Bc */ + { ARGSFL_NONE, args_Bf }, /* Bf */ + { ARGSFL_NONE, NULL }, /* Bo */ + { ARGSFL_DELIM, NULL }, /* Bq */ + { ARGSFL_DELIM, NULL }, /* Bsx */ + { ARGSFL_DELIM, NULL }, /* Bx */ + { ARGSFL_NONE, NULL }, /* Db */ + { ARGSFL_DELIM, NULL }, /* Dc */ + { ARGSFL_NONE, NULL }, /* Do */ + { ARGSFL_DELIM, NULL }, /* Dq */ + { ARGSFL_DELIM, NULL }, /* Ec */ + { ARGSFL_NONE, NULL }, /* Ef */ + { ARGSFL_DELIM, NULL }, /* Em */ + { ARGSFL_NONE, NULL }, /* Eo */ + { ARGSFL_DELIM, NULL }, /* Fx */ + { ARGSFL_DELIM, NULL }, /* Ms */ + { ARGSFL_DELIM, NULL }, /* No */ + { ARGSFL_DELIM, NULL }, /* Ns */ + { ARGSFL_DELIM, NULL }, /* Nx */ + { ARGSFL_DELIM, NULL }, /* Ox */ + { ARGSFL_DELIM, NULL }, /* Pc */ + { ARGSFL_DELIM, NULL }, /* Pf */ + { ARGSFL_NONE, NULL }, /* Po */ + { ARGSFL_DELIM, NULL }, /* Pq */ + { ARGSFL_DELIM, NULL }, /* Qc */ + { ARGSFL_DELIM, NULL }, /* Ql */ + { ARGSFL_NONE, NULL }, /* Qo */ + { ARGSFL_DELIM, NULL }, /* Qq */ + { ARGSFL_NONE, NULL }, /* Re */ + { ARGSFL_NONE, NULL }, /* Rs */ + { ARGSFL_DELIM, NULL }, /* Sc */ + { ARGSFL_NONE, NULL }, /* So */ + { ARGSFL_DELIM, NULL }, /* Sq */ + { ARGSFL_NONE, NULL }, /* Sm */ + { ARGSFL_DELIM, NULL }, /* Sx */ + { ARGSFL_DELIM, NULL }, /* Sy */ + { ARGSFL_DELIM, NULL }, /* Tn */ + { ARGSFL_DELIM, NULL }, /* Ux */ + { ARGSFL_DELIM, NULL }, /* Xc */ + { ARGSFL_NONE, NULL }, /* Xo */ + { ARGSFL_NONE, NULL }, /* Fo */ + { ARGSFL_NONE, NULL }, /* Fc */ + { ARGSFL_NONE, NULL }, /* Oo */ + { ARGSFL_DELIM, NULL }, /* Oc */ + { ARGSFL_NONE, args_Bk }, /* Bk */ + { ARGSFL_NONE, NULL }, /* Ek */ + { ARGSFL_NONE, NULL }, /* Bt */ + { ARGSFL_NONE, NULL }, /* Hf */ + { ARGSFL_NONE, NULL }, /* Fr */ + { ARGSFL_NONE, NULL }, /* Ud */ + { ARGSFL_NONE, NULL }, /* Lb */ + { ARGSFL_NONE, NULL }, /* Lp */ + { ARGSFL_DELIM, NULL }, /* Lk */ + { ARGSFL_DELIM, NULL }, /* Mt */ + { ARGSFL_DELIM, NULL }, /* Brq */ + { ARGSFL_NONE, NULL }, /* Bro */ + { ARGSFL_DELIM, NULL }, /* Brc */ + { ARGSFL_NONE, NULL }, /* %C */ + { ARGSFL_NONE, NULL }, /* Es */ + { ARGSFL_NONE, NULL }, /* En */ + { ARGSFL_NONE, NULL }, /* Dx */ + { ARGSFL_NONE, NULL }, /* %Q */ + { ARGSFL_NONE, NULL }, /* br */ + { ARGSFL_NONE, NULL }, /* sp */ + { ARGSFL_NONE, NULL }, /* %U */ + { ARGSFL_NONE, NULL }, /* Ta */ +}; + + +/* + * Parse an argument from line text. This comes in the form of -key + * [value0...], which may either have a single mandatory value, at least + * one mandatory value, an optional single value, or no value. + */ +enum margverr +mdoc_argv(struct mdoc *m, int line, enum mdoct tok, + struct mdoc_arg **v, int *pos, char *buf) +{ + char *p, sv; + struct mdoc_argv tmp; + struct mdoc_arg *arg; + const enum mdocargt *ap; + + if ('\0' == buf[*pos]) + return(ARGV_EOLN); + else if (NULL == (ap = mdocargs[tok].argvs)) + return(ARGV_WORD); + else if ('-' != buf[*pos]) + return(ARGV_WORD); + + /* Seek to the first unescaped space. */ + + p = &buf[++(*pos)]; + + assert(*pos > 0); + + for ( ; buf[*pos] ; (*pos)++) + if (' ' == buf[*pos] && '\\' != buf[*pos - 1]) + break; + + /* + * We want to nil-terminate the word to look it up (it's easier + * that way). But we may not have a flag, in which case we need + * to restore the line as-is. So keep around the stray byte, + * which we'll reset upon exiting (if necessary). + */ + + if ('\0' != (sv = buf[*pos])) + buf[(*pos)++] = '\0'; + + /* + * Now look up the word as a flag. Use temporary storage that + * we'll copy into the node's flags, if necessary. + */ + + memset(&tmp, 0, sizeof(struct mdoc_argv)); + + tmp.line = line; + tmp.pos = *pos; + tmp.arg = MDOC_ARG_MAX; + + while (MDOC_ARG_MAX != (tmp.arg = *ap++)) + if (0 == strcmp(p, mdoc_argnames[tmp.arg])) + break; + + if (MDOC_ARG_MAX == tmp.arg) { + /* + * The flag was not found. + * Restore saved zeroed byte and return as a word. + */ + if (sv) + buf[*pos - 1] = sv; + return(ARGV_WORD); + } + + /* Read to the next word (the argument). */ + + while (buf[*pos] && ' ' == buf[*pos]) + (*pos)++; + + switch (argvflags[tmp.arg]) { + case (ARGV_SINGLE): + if ( ! argv_single(m, line, &tmp, pos, buf)) + return(ARGV_ERROR); + break; + case (ARGV_MULTI): + if ( ! argv_multi(m, line, &tmp, pos, buf)) + return(ARGV_ERROR); + break; + case (ARGV_OPT_SINGLE): + if ( ! argv_opt_single(m, line, &tmp, pos, buf)) + return(ARGV_ERROR); + break; + case (ARGV_NONE): + break; + } + + if (NULL == (arg = *v)) + arg = *v = mandoc_calloc(1, sizeof(struct mdoc_arg)); + + arg->argc++; + arg->argv = mandoc_realloc + (arg->argv, arg->argc * sizeof(struct mdoc_argv)); + + memcpy(&arg->argv[(int)arg->argc - 1], + &tmp, sizeof(struct mdoc_argv)); + + return(ARGV_ARG); +} + +void +mdoc_argv_free(struct mdoc_arg *p) +{ + int i; + + if (NULL == p) + return; + + if (p->refcnt) { + --(p->refcnt); + if (p->refcnt) + return; + } + assert(p->argc); + + for (i = (int)p->argc - 1; i >= 0; i--) + argn_free(p, i); + + free(p->argv); + free(p); +} + +static void +argn_free(struct mdoc_arg *p, int iarg) +{ + struct mdoc_argv *arg; + int j; + + arg = &p->argv[iarg]; + + if (arg->sz && arg->value) { + for (j = (int)arg->sz - 1; j >= 0; j--) + free(arg->value[j]); + free(arg->value); + } + + for (--p->argc; iarg < (int)p->argc; iarg++) + p->argv[iarg] = p->argv[iarg+1]; +} + +enum margserr +mdoc_zargs(struct mdoc *m, int line, int *pos, char *buf, char **v) +{ + + return(args(m, line, pos, buf, ARGSFL_NONE, v)); +} + +enum margserr +mdoc_args(struct mdoc *m, int line, int *pos, + char *buf, enum mdoct tok, char **v) +{ + enum argsflag fl; + struct mdoc_node *n; + + fl = mdocargs[tok].flags; + + if (MDOC_It != tok) + return(args(m, line, pos, buf, fl, v)); + + /* + * We know that we're in an `It', so it's reasonable to expect + * us to be sitting in a `Bl'. Someday this may not be the case + * (if we allow random `It's sitting out there), so provide a + * safe fall-back into the default behaviour. + */ + + for (n = m->last; n; n = n->parent) + if (MDOC_Bl == n->tok) + if (LIST_column == n->norm->Bl.type) { + fl = ARGSFL_TABSEP; + break; + } + + return(args(m, line, pos, buf, fl, v)); +} + +static enum margserr +args(struct mdoc *m, int line, int *pos, + char *buf, enum argsflag fl, char **v) +{ + char *p, *pp; + enum margserr rc; + + if ('\0' == buf[*pos]) { + if (MDOC_PPHRASE & m->flags) + return(ARGS_EOLN); + /* + * If we're not in a partial phrase and the flag for + * being a phrase literal is still set, the punctuation + * is unterminated. + */ + if (MDOC_PHRASELIT & m->flags) + mdoc_pmsg(m, line, *pos, MANDOCERR_BADQUOTE); + + m->flags &= ~MDOC_PHRASELIT; + return(ARGS_EOLN); + } + + *v = &buf[*pos]; + + if (ARGSFL_DELIM == fl) + if (args_checkpunct(buf, *pos)) + return(ARGS_PUNCT); + + /* + * First handle TABSEP items, restricted to `Bl -column'. This + * ignores conventional token parsing and instead uses tabs or + * `Ta' macros to separate phrases. Phrases are parsed again + * for arguments at a later phase. + */ + + if (ARGSFL_TABSEP == fl) { + /* Scan ahead to tab (can't be escaped). */ + p = strchr(*v, '\t'); + pp = NULL; + + /* Scan ahead to unescaped `Ta'. */ + if ( ! (MDOC_PHRASELIT & m->flags)) + for (pp = *v; ; pp++) { + if (NULL == (pp = strstr(pp, "Ta"))) + break; + if (pp > *v && ' ' != *(pp - 1)) + continue; + if (' ' == *(pp + 2) || '\0' == *(pp + 2)) + break; + } + + /* By default, assume a phrase. */ + rc = ARGS_PHRASE; + + /* + * Adjust new-buffer position to be beyond delimiter + * mark (e.g., Ta -> end + 2). + */ + if (p && pp) { + *pos += pp < p ? 2 : 1; + rc = pp < p ? ARGS_PHRASE : ARGS_PPHRASE; + p = pp < p ? pp : p; + } else if (p && ! pp) { + rc = ARGS_PPHRASE; + *pos += 1; + } else if (pp && ! p) { + p = pp; + *pos += 2; + } else { + rc = ARGS_PEND; + p = strchr(*v, 0); + } + + /* Whitespace check for eoln case... */ + if ('\0' == *p && ' ' == *(p - 1)) + mdoc_pmsg(m, line, *pos, MANDOCERR_EOLNSPACE); + + *pos += (int)(p - *v); + + /* Strip delimiter's preceding whitespace. */ + pp = p - 1; + while (pp > *v && ' ' == *pp) { + if (pp > *v && '\\' == *(pp - 1)) + break; + pp--; + } + *(pp + 1) = 0; + + /* Strip delimiter's proceeding whitespace. */ + for (pp = &buf[*pos]; ' ' == *pp; pp++, (*pos)++) + /* Skip ahead. */ ; + + return(rc); + } + + /* + * Process a quoted literal. A quote begins with a double-quote + * and ends with a double-quote NOT preceded by a double-quote. + * Whitespace is NOT involved in literal termination. + */ + + if (MDOC_PHRASELIT & m->flags || '\"' == buf[*pos]) { + if ( ! (MDOC_PHRASELIT & m->flags)) + *v = &buf[++(*pos)]; + + if (MDOC_PPHRASE & m->flags) + m->flags |= MDOC_PHRASELIT; + + for ( ; buf[*pos]; (*pos)++) { + if ('\"' != buf[*pos]) + continue; + if ('\"' != buf[*pos + 1]) + break; + (*pos)++; + } + + if ('\0' == buf[*pos]) { + if (MDOC_PPHRASE & m->flags) + return(ARGS_QWORD); + mdoc_pmsg(m, line, *pos, MANDOCERR_BADQUOTE); + return(ARGS_QWORD); + } + + m->flags &= ~MDOC_PHRASELIT; + buf[(*pos)++] = '\0'; + + if ('\0' == buf[*pos]) + return(ARGS_QWORD); + + while (' ' == buf[*pos]) + (*pos)++; + + if ('\0' == buf[*pos]) + mdoc_pmsg(m, line, *pos, MANDOCERR_EOLNSPACE); + + return(ARGS_QWORD); + } + + p = &buf[*pos]; + *v = mandoc_getarg(m->parse, &p, line, pos); + + return(ARGS_WORD); +} + +/* + * Check if the string consists only of space-separated closing + * delimiters. This is a bit of a dance: the first must be a close + * delimiter, but it may be followed by middle delimiters. Arbitrary + * whitespace may separate these tokens. + */ +static int +args_checkpunct(const char *buf, int i) +{ + int j; + char dbuf[DELIMSZ]; + enum mdelim d; + + /* First token must be a close-delimiter. */ + + for (j = 0; buf[i] && ' ' != buf[i] && j < DELIMSZ; j++, i++) + dbuf[j] = buf[i]; + + if (DELIMSZ == j) + return(0); + + dbuf[j] = '\0'; + if (DELIM_CLOSE != mdoc_isdelim(dbuf)) + return(0); + + while (' ' == buf[i]) + i++; + + /* Remaining must NOT be open/none. */ + + while (buf[i]) { + j = 0; + while (buf[i] && ' ' != buf[i] && j < DELIMSZ) + dbuf[j++] = buf[i++]; + + if (DELIMSZ == j) + return(0); + + dbuf[j] = '\0'; + d = mdoc_isdelim(dbuf); + if (DELIM_NONE == d || DELIM_OPEN == d) + return(0); + + while (' ' == buf[i]) + i++; + } + + return('\0' == buf[i]); +} + +static int +argv_multi(struct mdoc *m, int line, + struct mdoc_argv *v, int *pos, char *buf) +{ + enum margserr ac; + char *p; + + for (v->sz = 0; ; v->sz++) { + if ('-' == buf[*pos]) + break; + ac = args(m, line, pos, buf, ARGSFL_NONE, &p); + if (ARGS_ERROR == ac) + return(0); + else if (ARGS_EOLN == ac) + break; + + if (0 == v->sz % MULTI_STEP) + v->value = mandoc_realloc(v->value, + (v->sz + MULTI_STEP) * sizeof(char *)); + + v->value[(int)v->sz] = mandoc_strdup(p); + } + + return(1); +} + +static int +argv_opt_single(struct mdoc *m, int line, + struct mdoc_argv *v, int *pos, char *buf) +{ + enum margserr ac; + char *p; + + if ('-' == buf[*pos]) + return(1); + + ac = args(m, line, pos, buf, ARGSFL_NONE, &p); + if (ARGS_ERROR == ac) + return(0); + if (ARGS_EOLN == ac) + return(1); + + v->sz = 1; + v->value = mandoc_malloc(sizeof(char *)); + v->value[0] = mandoc_strdup(p); + + return(1); +} + +static int +argv_single(struct mdoc *m, int line, + struct mdoc_argv *v, int *pos, char *buf) +{ + int ppos; + enum margserr ac; + char *p; + + ppos = *pos; + + ac = args(m, line, pos, buf, ARGSFL_NONE, &p); + if (ARGS_EOLN == ac) { + mdoc_pmsg(m, line, ppos, MANDOCERR_SYNTARGVCOUNT); + return(0); + } else if (ARGS_ERROR == ac) + return(0); + + v->sz = 1; + v->value = mandoc_malloc(sizeof(char *)); + v->value[0] = mandoc_strdup(p); + + return(1); +} diff --git a/mdoc_hash.c b/mdoc_hash.c new file mode 100644 index 000000000000..59a8d26a8817 --- /dev/null +++ b/mdoc_hash.c @@ -0,0 +1,94 @@ +/* $Id: mdoc_hash.c,v 1.18 2011/07/24 18:15:14 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/types.h> + +#include <assert.h> +#include <ctype.h> +#include <limits.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include "mdoc.h" +#include "mandoc.h" +#include "libmdoc.h" + +static unsigned char table[27 * 12]; + +/* + * XXX - this hash has global scope, so if intended for use as a library + * with multiple callers, it will need re-invocation protection. + */ +void +mdoc_hash_init(void) +{ + int i, j, major; + const char *p; + + memset(table, UCHAR_MAX, sizeof(table)); + + for (i = 0; i < (int)MDOC_MAX; i++) { + p = mdoc_macronames[i]; + + if (isalpha((unsigned char)p[1])) + major = 12 * (tolower((unsigned char)p[1]) - 97); + else + major = 12 * 26; + + for (j = 0; j < 12; j++) + if (UCHAR_MAX == table[major + j]) { + table[major + j] = (unsigned char)i; + break; + } + + assert(j < 12); + } +} + +enum mdoct +mdoc_hash_find(const char *p) +{ + int major, i, j; + + if (0 == p[0]) + return(MDOC_MAX); + if ( ! isalpha((unsigned char)p[0]) && '%' != p[0]) + return(MDOC_MAX); + + if (isalpha((unsigned char)p[1])) + major = 12 * (tolower((unsigned char)p[1]) - 97); + else if ('1' == p[1]) + major = 12 * 26; + else + return(MDOC_MAX); + + if (p[2] && p[3]) + return(MDOC_MAX); + + for (j = 0; j < 12; j++) { + if (UCHAR_MAX == (i = table[major + j])) + break; + if (0 == strcmp(p, mdoc_macronames[i])) + return((enum mdoct)i); + } + + return(MDOC_MAX); +} diff --git a/mdoc_html.c b/mdoc_html.c new file mode 100644 index 000000000000..60ea6dc73830 --- /dev/null +++ b/mdoc_html.c @@ -0,0 +1,2284 @@ +/* $Id: mdoc_html.c,v 1.182 2011/11/03 20:37:00 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/types.h> + +#include <assert.h> +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "mandoc.h" +#include "out.h" +#include "html.h" +#include "mdoc.h" +#include "main.h" + +#define INDENT 5 + +#define MDOC_ARGS const struct mdoc_meta *m, \ + const struct mdoc_node *n, \ + struct html *h + +#ifndef MIN +#define MIN(a,b) ((/*CONSTCOND*/(a)<(b))?(a):(b)) +#endif + +struct htmlmdoc { + int (*pre)(MDOC_ARGS); + void (*post)(MDOC_ARGS); +}; + +static void print_mdoc(MDOC_ARGS); +static void print_mdoc_head(MDOC_ARGS); +static void print_mdoc_node(MDOC_ARGS); +static void print_mdoc_nodelist(MDOC_ARGS); +static void synopsis_pre(struct html *, + const struct mdoc_node *); + +static void a2width(const char *, struct roffsu *); +static void a2offs(const char *, struct roffsu *); + +static void mdoc_root_post(MDOC_ARGS); +static int mdoc_root_pre(MDOC_ARGS); + +static void mdoc__x_post(MDOC_ARGS); +static int mdoc__x_pre(MDOC_ARGS); +static int mdoc_ad_pre(MDOC_ARGS); +static int mdoc_an_pre(MDOC_ARGS); +static int mdoc_ap_pre(MDOC_ARGS); +static int mdoc_ar_pre(MDOC_ARGS); +static int mdoc_bd_pre(MDOC_ARGS); +static int mdoc_bf_pre(MDOC_ARGS); +static void mdoc_bk_post(MDOC_ARGS); +static int mdoc_bk_pre(MDOC_ARGS); +static int mdoc_bl_pre(MDOC_ARGS); +static int mdoc_bt_pre(MDOC_ARGS); +static int mdoc_bx_pre(MDOC_ARGS); +static int mdoc_cd_pre(MDOC_ARGS); +static int mdoc_d1_pre(MDOC_ARGS); +static int mdoc_dv_pre(MDOC_ARGS); +static int mdoc_fa_pre(MDOC_ARGS); +static int mdoc_fd_pre(MDOC_ARGS); +static int mdoc_fl_pre(MDOC_ARGS); +static int mdoc_fn_pre(MDOC_ARGS); +static int mdoc_ft_pre(MDOC_ARGS); +static int mdoc_em_pre(MDOC_ARGS); +static int mdoc_er_pre(MDOC_ARGS); +static int mdoc_ev_pre(MDOC_ARGS); +static int mdoc_ex_pre(MDOC_ARGS); +static void mdoc_fo_post(MDOC_ARGS); +static int mdoc_fo_pre(MDOC_ARGS); +static int mdoc_ic_pre(MDOC_ARGS); +static int mdoc_igndelim_pre(MDOC_ARGS); +static int mdoc_in_pre(MDOC_ARGS); +static int mdoc_it_pre(MDOC_ARGS); +static int mdoc_lb_pre(MDOC_ARGS); +static int mdoc_li_pre(MDOC_ARGS); +static int mdoc_lk_pre(MDOC_ARGS); +static int mdoc_mt_pre(MDOC_ARGS); +static int mdoc_ms_pre(MDOC_ARGS); +static int mdoc_nd_pre(MDOC_ARGS); +static int mdoc_nm_pre(MDOC_ARGS); +static int mdoc_ns_pre(MDOC_ARGS); +static int mdoc_pa_pre(MDOC_ARGS); +static void mdoc_pf_post(MDOC_ARGS); +static int mdoc_pp_pre(MDOC_ARGS); +static void mdoc_quote_post(MDOC_ARGS); +static int mdoc_quote_pre(MDOC_ARGS); +static int mdoc_rs_pre(MDOC_ARGS); +static int mdoc_rv_pre(MDOC_ARGS); +static int mdoc_sh_pre(MDOC_ARGS); +static int mdoc_sm_pre(MDOC_ARGS); +static int mdoc_sp_pre(MDOC_ARGS); +static int mdoc_ss_pre(MDOC_ARGS); +static int mdoc_sx_pre(MDOC_ARGS); +static int mdoc_sy_pre(MDOC_ARGS); +static int mdoc_ud_pre(MDOC_ARGS); +static int mdoc_va_pre(MDOC_ARGS); +static int mdoc_vt_pre(MDOC_ARGS); +static int mdoc_xr_pre(MDOC_ARGS); +static int mdoc_xx_pre(MDOC_ARGS); + +static const struct htmlmdoc mdocs[MDOC_MAX] = { + {mdoc_ap_pre, NULL}, /* Ap */ + {NULL, NULL}, /* Dd */ + {NULL, NULL}, /* Dt */ + {NULL, NULL}, /* Os */ + {mdoc_sh_pre, NULL }, /* Sh */ + {mdoc_ss_pre, NULL }, /* Ss */ + {mdoc_pp_pre, NULL}, /* Pp */ + {mdoc_d1_pre, NULL}, /* D1 */ + {mdoc_d1_pre, NULL}, /* Dl */ + {mdoc_bd_pre, NULL}, /* Bd */ + {NULL, NULL}, /* Ed */ + {mdoc_bl_pre, NULL}, /* Bl */ + {NULL, NULL}, /* El */ + {mdoc_it_pre, NULL}, /* It */ + {mdoc_ad_pre, NULL}, /* Ad */ + {mdoc_an_pre, NULL}, /* An */ + {mdoc_ar_pre, NULL}, /* Ar */ + {mdoc_cd_pre, NULL}, /* Cd */ + {mdoc_fl_pre, NULL}, /* Cm */ + {mdoc_dv_pre, NULL}, /* Dv */ + {mdoc_er_pre, NULL}, /* Er */ + {mdoc_ev_pre, NULL}, /* Ev */ + {mdoc_ex_pre, NULL}, /* Ex */ + {mdoc_fa_pre, NULL}, /* Fa */ + {mdoc_fd_pre, NULL}, /* Fd */ + {mdoc_fl_pre, NULL}, /* Fl */ + {mdoc_fn_pre, NULL}, /* Fn */ + {mdoc_ft_pre, NULL}, /* Ft */ + {mdoc_ic_pre, NULL}, /* Ic */ + {mdoc_in_pre, NULL}, /* In */ + {mdoc_li_pre, NULL}, /* Li */ + {mdoc_nd_pre, NULL}, /* Nd */ + {mdoc_nm_pre, NULL}, /* Nm */ + {mdoc_quote_pre, mdoc_quote_post}, /* Op */ + {NULL, NULL}, /* Ot */ + {mdoc_pa_pre, NULL}, /* Pa */ + {mdoc_rv_pre, NULL}, /* Rv */ + {NULL, NULL}, /* St */ + {mdoc_va_pre, NULL}, /* Va */ + {mdoc_vt_pre, NULL}, /* Vt */ + {mdoc_xr_pre, NULL}, /* Xr */ + {mdoc__x_pre, mdoc__x_post}, /* %A */ + {mdoc__x_pre, mdoc__x_post}, /* %B */ + {mdoc__x_pre, mdoc__x_post}, /* %D */ + {mdoc__x_pre, mdoc__x_post}, /* %I */ + {mdoc__x_pre, mdoc__x_post}, /* %J */ + {mdoc__x_pre, mdoc__x_post}, /* %N */ + {mdoc__x_pre, mdoc__x_post}, /* %O */ + {mdoc__x_pre, mdoc__x_post}, /* %P */ + {mdoc__x_pre, mdoc__x_post}, /* %R */ + {mdoc__x_pre, mdoc__x_post}, /* %T */ + {mdoc__x_pre, mdoc__x_post}, /* %V */ + {NULL, NULL}, /* Ac */ + {mdoc_quote_pre, mdoc_quote_post}, /* Ao */ + {mdoc_quote_pre, mdoc_quote_post}, /* Aq */ + {NULL, NULL}, /* At */ + {NULL, NULL}, /* Bc */ + {mdoc_bf_pre, NULL}, /* Bf */ + {mdoc_quote_pre, mdoc_quote_post}, /* Bo */ + {mdoc_quote_pre, mdoc_quote_post}, /* Bq */ + {mdoc_xx_pre, NULL}, /* Bsx */ + {mdoc_bx_pre, NULL}, /* Bx */ + {NULL, NULL}, /* Db */ + {NULL, NULL}, /* Dc */ + {mdoc_quote_pre, mdoc_quote_post}, /* Do */ + {mdoc_quote_pre, mdoc_quote_post}, /* Dq */ + {NULL, NULL}, /* Ec */ /* FIXME: no space */ + {NULL, NULL}, /* Ef */ + {mdoc_em_pre, NULL}, /* Em */ + {mdoc_quote_pre, mdoc_quote_post}, /* Eo */ + {mdoc_xx_pre, NULL}, /* Fx */ + {mdoc_ms_pre, NULL}, /* Ms */ + {mdoc_igndelim_pre, NULL}, /* No */ + {mdoc_ns_pre, NULL}, /* Ns */ + {mdoc_xx_pre, NULL}, /* Nx */ + {mdoc_xx_pre, NULL}, /* Ox */ + {NULL, NULL}, /* Pc */ + {mdoc_igndelim_pre, mdoc_pf_post}, /* Pf */ + {mdoc_quote_pre, mdoc_quote_post}, /* Po */ + {mdoc_quote_pre, mdoc_quote_post}, /* Pq */ + {NULL, NULL}, /* Qc */ + {mdoc_quote_pre, mdoc_quote_post}, /* Ql */ + {mdoc_quote_pre, mdoc_quote_post}, /* Qo */ + {mdoc_quote_pre, mdoc_quote_post}, /* Qq */ + {NULL, NULL}, /* Re */ + {mdoc_rs_pre, NULL}, /* Rs */ + {NULL, NULL}, /* Sc */ + {mdoc_quote_pre, mdoc_quote_post}, /* So */ + {mdoc_quote_pre, mdoc_quote_post}, /* Sq */ + {mdoc_sm_pre, NULL}, /* Sm */ + {mdoc_sx_pre, NULL}, /* Sx */ + {mdoc_sy_pre, NULL}, /* Sy */ + {NULL, NULL}, /* Tn */ + {mdoc_xx_pre, NULL}, /* Ux */ + {NULL, NULL}, /* Xc */ + {NULL, NULL}, /* Xo */ + {mdoc_fo_pre, mdoc_fo_post}, /* Fo */ + {NULL, NULL}, /* Fc */ + {mdoc_quote_pre, mdoc_quote_post}, /* Oo */ + {NULL, NULL}, /* Oc */ + {mdoc_bk_pre, mdoc_bk_post}, /* Bk */ + {NULL, NULL}, /* Ek */ + {mdoc_bt_pre, NULL}, /* Bt */ + {NULL, NULL}, /* Hf */ + {NULL, NULL}, /* Fr */ + {mdoc_ud_pre, NULL}, /* Ud */ + {mdoc_lb_pre, NULL}, /* Lb */ + {mdoc_pp_pre, NULL}, /* Lp */ + {mdoc_lk_pre, NULL}, /* Lk */ + {mdoc_mt_pre, NULL}, /* Mt */ + {mdoc_quote_pre, mdoc_quote_post}, /* Brq */ + {mdoc_quote_pre, mdoc_quote_post}, /* Bro */ + {NULL, NULL}, /* Brc */ + {mdoc__x_pre, mdoc__x_post}, /* %C */ + {NULL, NULL}, /* Es */ /* TODO */ + {NULL, NULL}, /* En */ /* TODO */ + {mdoc_xx_pre, NULL}, /* Dx */ + {mdoc__x_pre, mdoc__x_post}, /* %Q */ + {mdoc_sp_pre, NULL}, /* br */ + {mdoc_sp_pre, NULL}, /* sp */ + {mdoc__x_pre, mdoc__x_post}, /* %U */ + {NULL, NULL}, /* Ta */ +}; + +static const char * const lists[LIST_MAX] = { + NULL, + "list-bul", + "list-col", + "list-dash", + "list-diag", + "list-enum", + "list-hang", + "list-hyph", + "list-inset", + "list-item", + "list-ohang", + "list-tag" +}; + +void +html_mdoc(void *arg, const struct mdoc *m) +{ + + print_mdoc(mdoc_meta(m), mdoc_node(m), (struct html *)arg); + putchar('\n'); +} + + +/* + * Calculate the scaling unit passed in a `-width' argument. This uses + * either a native scaling unit (e.g., 1i, 2m) or the string length of + * the value. + */ +static void +a2width(const char *p, struct roffsu *su) +{ + + if ( ! a2roffsu(p, su, SCALE_MAX)) { + su->unit = SCALE_BU; + su->scale = html_strlen(p); + } +} + + +/* + * See the same function in mdoc_term.c for documentation. + */ +static void +synopsis_pre(struct html *h, const struct mdoc_node *n) +{ + + if (NULL == n->prev || ! (MDOC_SYNPRETTY & n->flags)) + return; + + if (n->prev->tok == n->tok && + MDOC_Fo != n->tok && + MDOC_Ft != n->tok && + MDOC_Fn != n->tok) { + print_otag(h, TAG_BR, 0, NULL); + return; + } + + switch (n->prev->tok) { + case (MDOC_Fd): + /* FALLTHROUGH */ + case (MDOC_Fn): + /* FALLTHROUGH */ + case (MDOC_Fo): + /* FALLTHROUGH */ + case (MDOC_In): + /* FALLTHROUGH */ + case (MDOC_Vt): + print_otag(h, TAG_P, 0, NULL); + break; + case (MDOC_Ft): + if (MDOC_Fn != n->tok && MDOC_Fo != n->tok) { + print_otag(h, TAG_P, 0, NULL); + break; + } + /* FALLTHROUGH */ + default: + print_otag(h, TAG_BR, 0, NULL); + break; + } +} + + +/* + * Calculate the scaling unit passed in an `-offset' argument. This + * uses either a native scaling unit (e.g., 1i, 2m), one of a set of + * predefined strings (indent, etc.), or the string length of the value. + */ +static void +a2offs(const char *p, struct roffsu *su) +{ + + /* FIXME: "right"? */ + + if (0 == strcmp(p, "left")) + SCALE_HS_INIT(su, 0); + else if (0 == strcmp(p, "indent")) + SCALE_HS_INIT(su, INDENT); + else if (0 == strcmp(p, "indent-two")) + SCALE_HS_INIT(su, INDENT * 2); + else if ( ! a2roffsu(p, su, SCALE_MAX)) + SCALE_HS_INIT(su, html_strlen(p)); +} + + +static void +print_mdoc(MDOC_ARGS) +{ + struct tag *t, *tt; + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "mandoc"); + + if ( ! (HTML_FRAGMENT & h->oflags)) { + print_gen_decls(h); + t = print_otag(h, TAG_HTML, 0, NULL); + tt = print_otag(h, TAG_HEAD, 0, NULL); + print_mdoc_head(m, n, h); + print_tagq(h, tt); + print_otag(h, TAG_BODY, 0, NULL); + print_otag(h, TAG_DIV, 1, &tag); + } else + t = print_otag(h, TAG_DIV, 1, &tag); + + print_mdoc_nodelist(m, n, h); + print_tagq(h, t); +} + + +/* ARGSUSED */ +static void +print_mdoc_head(MDOC_ARGS) +{ + + print_gen_head(h); + bufinit(h); + bufcat_fmt(h, "%s(%s)", m->title, m->msec); + + if (m->arch) + bufcat_fmt(h, " (%s)", m->arch); + + print_otag(h, TAG_TITLE, 0, NULL); + print_text(h, h->buf); +} + + +static void +print_mdoc_nodelist(MDOC_ARGS) +{ + + print_mdoc_node(m, n, h); + if (n->next) + print_mdoc_nodelist(m, n->next, h); +} + + +static void +print_mdoc_node(MDOC_ARGS) +{ + int child; + struct tag *t; + + child = 1; + t = h->tags.head; + + switch (n->type) { + case (MDOC_ROOT): + child = mdoc_root_pre(m, n, h); + break; + case (MDOC_TEXT): + /* No tables in this mode... */ + assert(NULL == h->tblt); + + /* + * Make sure that if we're in a literal mode already + * (i.e., within a <PRE>) don't print the newline. + */ + if (' ' == *n->string && MDOC_LINE & n->flags) + if ( ! (HTML_LITERAL & h->flags)) + print_otag(h, TAG_BR, 0, NULL); + if (MDOC_DELIMC & n->flags) + h->flags |= HTML_NOSPACE; + print_text(h, n->string); + if (MDOC_DELIMO & n->flags) + h->flags |= HTML_NOSPACE; + return; + case (MDOC_EQN): + print_eqn(h, n->eqn); + break; + case (MDOC_TBL): + /* + * This will take care of initialising all of the table + * state data for the first table, then tearing it down + * for the last one. + */ + print_tbl(h, n->span); + return; + default: + /* + * Close out the current table, if it's open, and unset + * the "meta" table state. This will be reopened on the + * next table element. + */ + if (h->tblt) { + print_tblclose(h); + t = h->tags.head; + } + + assert(NULL == h->tblt); + if (mdocs[n->tok].pre && ENDBODY_NOT == n->end) + child = (*mdocs[n->tok].pre)(m, n, h); + break; + } + + if (HTML_KEEP & h->flags) { + if (n->prev && n->prev->line != n->line) { + h->flags &= ~HTML_KEEP; + h->flags |= HTML_PREKEEP; + } else if (NULL == n->prev) { + if (n->parent && n->parent->line != n->line) { + h->flags &= ~HTML_KEEP; + h->flags |= HTML_PREKEEP; + } + } + } + + if (child && n->child) + print_mdoc_nodelist(m, n->child, h); + + print_stagq(h, t); + + switch (n->type) { + case (MDOC_ROOT): + mdoc_root_post(m, n, h); + break; + case (MDOC_EQN): + break; + default: + if (mdocs[n->tok].post && ENDBODY_NOT == n->end) + (*mdocs[n->tok].post)(m, n, h); + break; + } +} + +/* ARGSUSED */ +static void +mdoc_root_post(MDOC_ARGS) +{ + struct htmlpair tag[3]; + struct tag *t, *tt; + + PAIR_SUMMARY_INIT(&tag[0], "Document Footer"); + PAIR_CLASS_INIT(&tag[1], "foot"); + PAIR_INIT(&tag[2], ATTR_WIDTH, "100%"); + t = print_otag(h, TAG_TABLE, 3, tag); + PAIR_INIT(&tag[0], ATTR_WIDTH, "50%"); + print_otag(h, TAG_COL, 1, tag); + print_otag(h, TAG_COL, 1, tag); + + print_otag(h, TAG_TBODY, 0, NULL); + + tt = print_otag(h, TAG_TR, 0, NULL); + + PAIR_CLASS_INIT(&tag[0], "foot-date"); + print_otag(h, TAG_TD, 1, tag); + print_text(h, m->date); + print_stagq(h, tt); + + PAIR_CLASS_INIT(&tag[0], "foot-os"); + PAIR_INIT(&tag[1], ATTR_ALIGN, "right"); + print_otag(h, TAG_TD, 2, tag); + print_text(h, m->os); + print_tagq(h, t); +} + + +/* ARGSUSED */ +static int +mdoc_root_pre(MDOC_ARGS) +{ + struct htmlpair tag[3]; + struct tag *t, *tt; + char b[BUFSIZ], title[BUFSIZ]; + + strlcpy(b, m->vol, BUFSIZ); + + if (m->arch) { + strlcat(b, " (", BUFSIZ); + strlcat(b, m->arch, BUFSIZ); + strlcat(b, ")", BUFSIZ); + } + + snprintf(title, BUFSIZ - 1, "%s(%s)", m->title, m->msec); + + PAIR_SUMMARY_INIT(&tag[0], "Document Header"); + PAIR_CLASS_INIT(&tag[1], "head"); + PAIR_INIT(&tag[2], ATTR_WIDTH, "100%"); + t = print_otag(h, TAG_TABLE, 3, tag); + PAIR_INIT(&tag[0], ATTR_WIDTH, "30%"); + print_otag(h, TAG_COL, 1, tag); + print_otag(h, TAG_COL, 1, tag); + print_otag(h, TAG_COL, 1, tag); + + print_otag(h, TAG_TBODY, 0, NULL); + + tt = print_otag(h, TAG_TR, 0, NULL); + + PAIR_CLASS_INIT(&tag[0], "head-ltitle"); + print_otag(h, TAG_TD, 1, tag); + print_text(h, title); + print_stagq(h, tt); + + PAIR_CLASS_INIT(&tag[0], "head-vol"); + PAIR_INIT(&tag[1], ATTR_ALIGN, "center"); + print_otag(h, TAG_TD, 2, tag); + print_text(h, b); + print_stagq(h, tt); + + PAIR_CLASS_INIT(&tag[0], "head-rtitle"); + PAIR_INIT(&tag[1], ATTR_ALIGN, "right"); + print_otag(h, TAG_TD, 2, tag); + print_text(h, title); + print_tagq(h, t); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_sh_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + if (MDOC_BLOCK == n->type) { + PAIR_CLASS_INIT(&tag, "section"); + print_otag(h, TAG_DIV, 1, &tag); + return(1); + } else if (MDOC_BODY == n->type) + return(1); + + bufinit(h); + bufcat(h, "x"); + + for (n = n->child; n && MDOC_TEXT == n->type; ) { + bufcat_id(h, n->string); + if (NULL != (n = n->next)) + bufcat_id(h, " "); + } + + if (NULL == n) { + PAIR_ID_INIT(&tag, h->buf); + print_otag(h, TAG_H1, 1, &tag); + } else + print_otag(h, TAG_H1, 0, NULL); + + return(1); +} + +/* ARGSUSED */ +static int +mdoc_ss_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + if (MDOC_BLOCK == n->type) { + PAIR_CLASS_INIT(&tag, "subsection"); + print_otag(h, TAG_DIV, 1, &tag); + return(1); + } else if (MDOC_BODY == n->type) + return(1); + + bufinit(h); + bufcat(h, "x"); + + for (n = n->child; n && MDOC_TEXT == n->type; ) { + bufcat_id(h, n->string); + if (NULL != (n = n->next)) + bufcat_id(h, " "); + } + + if (NULL == n) { + PAIR_ID_INIT(&tag, h->buf); + print_otag(h, TAG_H2, 1, &tag); + } else + print_otag(h, TAG_H2, 0, NULL); + + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_fl_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "flag"); + print_otag(h, TAG_B, 1, &tag); + + /* `Cm' has no leading hyphen. */ + + if (MDOC_Cm == n->tok) + return(1); + + print_text(h, "\\-"); + + if (n->child) + h->flags |= HTML_NOSPACE; + else if (n->next && n->next->line == n->line) + h->flags |= HTML_NOSPACE; + + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_nd_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + if (MDOC_BODY != n->type) + return(1); + + /* XXX: this tag in theory can contain block elements. */ + + print_text(h, "\\(em"); + PAIR_CLASS_INIT(&tag, "desc"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +static int +mdoc_nm_pre(MDOC_ARGS) +{ + struct htmlpair tag; + struct roffsu su; + int len; + + switch (n->type) { + case (MDOC_ELEM): + synopsis_pre(h, n); + PAIR_CLASS_INIT(&tag, "name"); + print_otag(h, TAG_B, 1, &tag); + if (NULL == n->child && m->name) + print_text(h, m->name); + return(1); + case (MDOC_HEAD): + print_otag(h, TAG_TD, 0, NULL); + if (NULL == n->child && m->name) + print_text(h, m->name); + return(1); + case (MDOC_BODY): + print_otag(h, TAG_TD, 0, NULL); + return(1); + default: + break; + } + + synopsis_pre(h, n); + PAIR_CLASS_INIT(&tag, "synopsis"); + print_otag(h, TAG_TABLE, 1, &tag); + + for (len = 0, n = n->child; n; n = n->next) + if (MDOC_TEXT == n->type) + len += html_strlen(n->string); + + if (0 == len && m->name) + len = html_strlen(m->name); + + SCALE_HS_INIT(&su, (double)len); + bufinit(h); + bufcat_su(h, "width", &su); + PAIR_STYLE_INIT(&tag, h); + print_otag(h, TAG_COL, 1, &tag); + print_otag(h, TAG_COL, 0, NULL); + print_otag(h, TAG_TBODY, 0, NULL); + print_otag(h, TAG_TR, 0, NULL); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_xr_pre(MDOC_ARGS) +{ + struct htmlpair tag[2]; + + if (NULL == n->child) + return(0); + + PAIR_CLASS_INIT(&tag[0], "link-man"); + + if (h->base_man) { + buffmt_man(h, n->child->string, + n->child->next ? + n->child->next->string : NULL); + PAIR_HREF_INIT(&tag[1], h->buf); + print_otag(h, TAG_A, 2, tag); + } else + print_otag(h, TAG_A, 1, tag); + + n = n->child; + print_text(h, n->string); + + if (NULL == (n = n->next)) + return(0); + + h->flags |= HTML_NOSPACE; + print_text(h, "("); + h->flags |= HTML_NOSPACE; + print_text(h, n->string); + h->flags |= HTML_NOSPACE; + print_text(h, ")"); + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_ns_pre(MDOC_ARGS) +{ + + if ( ! (MDOC_LINE & n->flags)) + h->flags |= HTML_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_ar_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "arg"); + print_otag(h, TAG_I, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_xx_pre(MDOC_ARGS) +{ + const char *pp; + struct htmlpair tag; + int flags; + + switch (n->tok) { + case (MDOC_Bsx): + pp = "BSD/OS"; + break; + case (MDOC_Dx): + pp = "DragonFly"; + break; + case (MDOC_Fx): + pp = "FreeBSD"; + break; + case (MDOC_Nx): + pp = "NetBSD"; + break; + case (MDOC_Ox): + pp = "OpenBSD"; + break; + case (MDOC_Ux): + pp = "UNIX"; + break; + default: + return(1); + } + + PAIR_CLASS_INIT(&tag, "unix"); + print_otag(h, TAG_SPAN, 1, &tag); + + print_text(h, pp); + if (n->child) { + flags = h->flags; + h->flags |= HTML_KEEP; + print_text(h, n->child->string); + h->flags = flags; + } + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_bx_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "unix"); + print_otag(h, TAG_SPAN, 1, &tag); + + if (NULL != (n = n->child)) { + print_text(h, n->string); + h->flags |= HTML_NOSPACE; + print_text(h, "BSD"); + } else { + print_text(h, "BSD"); + return(0); + } + + if (NULL != (n = n->next)) { + h->flags |= HTML_NOSPACE; + print_text(h, "-"); + h->flags |= HTML_NOSPACE; + print_text(h, n->string); + } + + return(0); +} + +/* ARGSUSED */ +static int +mdoc_it_pre(MDOC_ARGS) +{ + struct roffsu su; + enum mdoc_list type; + struct htmlpair tag[2]; + const struct mdoc_node *bl; + + bl = n->parent; + while (bl && MDOC_Bl != bl->tok) + bl = bl->parent; + + assert(bl); + + type = bl->norm->Bl.type; + + assert(lists[type]); + PAIR_CLASS_INIT(&tag[0], lists[type]); + + bufinit(h); + + if (MDOC_HEAD == n->type) { + switch (type) { + case(LIST_bullet): + /* FALLTHROUGH */ + case(LIST_dash): + /* FALLTHROUGH */ + case(LIST_item): + /* FALLTHROUGH */ + case(LIST_hyphen): + /* FALLTHROUGH */ + case(LIST_enum): + return(0); + case(LIST_diag): + /* FALLTHROUGH */ + case(LIST_hang): + /* FALLTHROUGH */ + case(LIST_inset): + /* FALLTHROUGH */ + case(LIST_ohang): + /* FALLTHROUGH */ + case(LIST_tag): + SCALE_VS_INIT(&su, ! bl->norm->Bl.comp); + bufcat_su(h, "margin-top", &su); + PAIR_STYLE_INIT(&tag[1], h); + print_otag(h, TAG_DT, 2, tag); + if (LIST_diag != type) + break; + PAIR_CLASS_INIT(&tag[0], "diag"); + print_otag(h, TAG_B, 1, tag); + break; + case(LIST_column): + break; + default: + break; + } + } else if (MDOC_BODY == n->type) { + switch (type) { + case(LIST_bullet): + /* FALLTHROUGH */ + case(LIST_hyphen): + /* FALLTHROUGH */ + case(LIST_dash): + /* FALLTHROUGH */ + case(LIST_enum): + /* FALLTHROUGH */ + case(LIST_item): + SCALE_VS_INIT(&su, ! bl->norm->Bl.comp); + bufcat_su(h, "margin-top", &su); + PAIR_STYLE_INIT(&tag[1], h); + print_otag(h, TAG_LI, 2, tag); + break; + case(LIST_diag): + /* FALLTHROUGH */ + case(LIST_hang): + /* FALLTHROUGH */ + case(LIST_inset): + /* FALLTHROUGH */ + case(LIST_ohang): + /* FALLTHROUGH */ + case(LIST_tag): + if (NULL == bl->norm->Bl.width) { + print_otag(h, TAG_DD, 1, tag); + break; + } + a2width(bl->norm->Bl.width, &su); + bufcat_su(h, "margin-left", &su); + PAIR_STYLE_INIT(&tag[1], h); + print_otag(h, TAG_DD, 2, tag); + break; + case(LIST_column): + SCALE_VS_INIT(&su, ! bl->norm->Bl.comp); + bufcat_su(h, "margin-top", &su); + PAIR_STYLE_INIT(&tag[1], h); + print_otag(h, TAG_TD, 2, tag); + break; + default: + break; + } + } else { + switch (type) { + case (LIST_column): + print_otag(h, TAG_TR, 1, tag); + break; + default: + break; + } + } + + return(1); +} + +/* ARGSUSED */ +static int +mdoc_bl_pre(MDOC_ARGS) +{ + int i; + struct htmlpair tag[3]; + struct roffsu su; + char buf[BUFSIZ]; + + bufinit(h); + + if (MDOC_BODY == n->type) { + if (LIST_column == n->norm->Bl.type) + print_otag(h, TAG_TBODY, 0, NULL); + return(1); + } + + if (MDOC_HEAD == n->type) { + if (LIST_column != n->norm->Bl.type) + return(0); + + /* + * For each column, print out the <COL> tag with our + * suggested width. The last column gets min-width, as + * in terminal mode it auto-sizes to the width of the + * screen and we want to preserve that behaviour. + */ + + for (i = 0; i < (int)n->norm->Bl.ncols; i++) { + a2width(n->norm->Bl.cols[i], &su); + if (i < (int)n->norm->Bl.ncols - 1) + bufcat_su(h, "width", &su); + else + bufcat_su(h, "min-width", &su); + PAIR_STYLE_INIT(&tag[0], h); + print_otag(h, TAG_COL, 1, tag); + } + + return(0); + } + + SCALE_VS_INIT(&su, 0); + bufcat_su(h, "margin-top", &su); + bufcat_su(h, "margin-bottom", &su); + PAIR_STYLE_INIT(&tag[0], h); + + assert(lists[n->norm->Bl.type]); + strlcpy(buf, "list ", BUFSIZ); + strlcat(buf, lists[n->norm->Bl.type], BUFSIZ); + PAIR_INIT(&tag[1], ATTR_CLASS, buf); + + /* Set the block's left-hand margin. */ + + if (n->norm->Bl.offs) { + a2offs(n->norm->Bl.offs, &su); + bufcat_su(h, "margin-left", &su); + } + + switch (n->norm->Bl.type) { + case(LIST_bullet): + /* FALLTHROUGH */ + case(LIST_dash): + /* FALLTHROUGH */ + case(LIST_hyphen): + /* FALLTHROUGH */ + case(LIST_item): + print_otag(h, TAG_UL, 2, tag); + break; + case(LIST_enum): + print_otag(h, TAG_OL, 2, tag); + break; + case(LIST_diag): + /* FALLTHROUGH */ + case(LIST_hang): + /* FALLTHROUGH */ + case(LIST_inset): + /* FALLTHROUGH */ + case(LIST_ohang): + /* FALLTHROUGH */ + case(LIST_tag): + print_otag(h, TAG_DL, 2, tag); + break; + case(LIST_column): + print_otag(h, TAG_TABLE, 2, tag); + break; + default: + abort(); + /* NOTREACHED */ + } + + return(1); +} + +/* ARGSUSED */ +static int +mdoc_ex_pre(MDOC_ARGS) +{ + struct tag *t; + struct htmlpair tag; + int nchild; + + if (n->prev) + print_otag(h, TAG_BR, 0, NULL); + + PAIR_CLASS_INIT(&tag, "utility"); + + print_text(h, "The"); + + nchild = n->nchild; + for (n = n->child; n; n = n->next) { + assert(MDOC_TEXT == n->type); + + t = print_otag(h, TAG_B, 1, &tag); + print_text(h, n->string); + print_tagq(h, t); + + if (nchild > 2 && n->next) { + h->flags |= HTML_NOSPACE; + print_text(h, ","); + } + + if (n->next && NULL == n->next->next) + print_text(h, "and"); + } + + if (nchild > 1) + print_text(h, "utilities exit"); + else + print_text(h, "utility exits"); + + print_text(h, "0 on success, and >0 if an error occurs."); + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_em_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "emph"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_d1_pre(MDOC_ARGS) +{ + struct htmlpair tag[2]; + struct roffsu su; + + if (MDOC_BLOCK != n->type) + return(1); + + SCALE_VS_INIT(&su, 0); + bufinit(h); + bufcat_su(h, "margin-top", &su); + bufcat_su(h, "margin-bottom", &su); + PAIR_STYLE_INIT(&tag[0], h); + print_otag(h, TAG_BLOCKQUOTE, 1, tag); + + /* BLOCKQUOTE needs a block body. */ + + PAIR_CLASS_INIT(&tag[0], "display"); + print_otag(h, TAG_DIV, 1, tag); + + if (MDOC_Dl == n->tok) { + PAIR_CLASS_INIT(&tag[0], "lit"); + print_otag(h, TAG_CODE, 1, tag); + } + + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_sx_pre(MDOC_ARGS) +{ + struct htmlpair tag[2]; + + bufinit(h); + bufcat(h, "#x"); + + for (n = n->child; n; ) { + bufcat_id(h, n->string); + if (NULL != (n = n->next)) + bufcat_id(h, " "); + } + + PAIR_CLASS_INIT(&tag[0], "link-sec"); + PAIR_HREF_INIT(&tag[1], h->buf); + + print_otag(h, TAG_I, 1, tag); + print_otag(h, TAG_A, 2, tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_bd_pre(MDOC_ARGS) +{ + struct htmlpair tag[2]; + int comp, sv; + const struct mdoc_node *nn; + struct roffsu su; + + if (MDOC_HEAD == n->type) + return(0); + + if (MDOC_BLOCK == n->type) { + comp = n->norm->Bd.comp; + for (nn = n; nn && ! comp; nn = nn->parent) { + if (MDOC_BLOCK != nn->type) + continue; + if (MDOC_Ss == nn->tok || MDOC_Sh == nn->tok) + comp = 1; + if (nn->prev) + break; + } + if ( ! comp) + print_otag(h, TAG_P, 0, NULL); + return(1); + } + + SCALE_HS_INIT(&su, 0); + if (n->norm->Bd.offs) + a2offs(n->norm->Bd.offs, &su); + + bufinit(h); + bufcat_su(h, "margin-left", &su); + PAIR_STYLE_INIT(&tag[0], h); + + if (DISP_unfilled != n->norm->Bd.type && + DISP_literal != n->norm->Bd.type) { + PAIR_CLASS_INIT(&tag[1], "display"); + print_otag(h, TAG_DIV, 2, tag); + return(1); + } + + PAIR_CLASS_INIT(&tag[1], "lit display"); + print_otag(h, TAG_PRE, 2, tag); + + /* This can be recursive: save & set our literal state. */ + + sv = h->flags & HTML_LITERAL; + h->flags |= HTML_LITERAL; + + for (nn = n->child; nn; nn = nn->next) { + print_mdoc_node(m, nn, h); + /* + * If the printed node flushes its own line, then we + * needn't do it here as well. This is hacky, but the + * notion of selective eoln whitespace is pretty dumb + * anyway, so don't sweat it. + */ + switch (nn->tok) { + case (MDOC_Sm): + /* FALLTHROUGH */ + case (MDOC_br): + /* FALLTHROUGH */ + case (MDOC_sp): + /* FALLTHROUGH */ + case (MDOC_Bl): + /* FALLTHROUGH */ + case (MDOC_D1): + /* FALLTHROUGH */ + case (MDOC_Dl): + /* FALLTHROUGH */ + case (MDOC_Lp): + /* FALLTHROUGH */ + case (MDOC_Pp): + continue; + default: + break; + } + if (nn->next && nn->next->line == nn->line) + continue; + else if (nn->next) + print_text(h, "\n"); + + h->flags |= HTML_NOSPACE; + } + + if (0 == sv) + h->flags &= ~HTML_LITERAL; + + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_pa_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "file"); + print_otag(h, TAG_I, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_ad_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "addr"); + print_otag(h, TAG_I, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_an_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + /* TODO: -split and -nosplit (see termp_an_pre()). */ + + PAIR_CLASS_INIT(&tag, "author"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_cd_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + synopsis_pre(h, n); + PAIR_CLASS_INIT(&tag, "config"); + print_otag(h, TAG_B, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_dv_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "define"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_ev_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "env"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_er_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "errno"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_fa_pre(MDOC_ARGS) +{ + const struct mdoc_node *nn; + struct htmlpair tag; + struct tag *t; + + PAIR_CLASS_INIT(&tag, "farg"); + if (n->parent->tok != MDOC_Fo) { + print_otag(h, TAG_I, 1, &tag); + return(1); + } + + for (nn = n->child; nn; nn = nn->next) { + t = print_otag(h, TAG_I, 1, &tag); + print_text(h, nn->string); + print_tagq(h, t); + if (nn->next) { + h->flags |= HTML_NOSPACE; + print_text(h, ","); + } + } + + if (n->child && n->next && n->next->tok == MDOC_Fa) { + h->flags |= HTML_NOSPACE; + print_text(h, ","); + } + + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_fd_pre(MDOC_ARGS) +{ + struct htmlpair tag[2]; + char buf[BUFSIZ]; + size_t sz; + int i; + struct tag *t; + + synopsis_pre(h, n); + + if (NULL == (n = n->child)) + return(0); + + assert(MDOC_TEXT == n->type); + + if (strcmp(n->string, "#include")) { + PAIR_CLASS_INIT(&tag[0], "macro"); + print_otag(h, TAG_B, 1, tag); + return(1); + } + + PAIR_CLASS_INIT(&tag[0], "includes"); + print_otag(h, TAG_B, 1, tag); + print_text(h, n->string); + + if (NULL != (n = n->next)) { + assert(MDOC_TEXT == n->type); + strlcpy(buf, '<' == *n->string || '"' == *n->string ? + n->string + 1 : n->string, BUFSIZ); + + sz = strlen(buf); + if (sz && ('>' == buf[sz - 1] || '"' == buf[sz - 1])) + buf[sz - 1] = '\0'; + + PAIR_CLASS_INIT(&tag[0], "link-includes"); + + i = 1; + if (h->base_includes) { + buffmt_includes(h, buf); + PAIR_HREF_INIT(&tag[i], h->buf); + i++; + } + + t = print_otag(h, TAG_A, i, tag); + print_text(h, n->string); + print_tagq(h, t); + + n = n->next; + } + + for ( ; n; n = n->next) { + assert(MDOC_TEXT == n->type); + print_text(h, n->string); + } + + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_vt_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + if (MDOC_BLOCK == n->type) { + synopsis_pre(h, n); + return(1); + } else if (MDOC_ELEM == n->type) { + synopsis_pre(h, n); + } else if (MDOC_HEAD == n->type) + return(0); + + PAIR_CLASS_INIT(&tag, "type"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_ft_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + synopsis_pre(h, n); + PAIR_CLASS_INIT(&tag, "ftype"); + print_otag(h, TAG_I, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_fn_pre(MDOC_ARGS) +{ + struct tag *t; + struct htmlpair tag[2]; + char nbuf[BUFSIZ]; + const char *sp, *ep; + int sz, i, pretty; + + pretty = MDOC_SYNPRETTY & n->flags; + synopsis_pre(h, n); + + /* Split apart into type and name. */ + assert(n->child->string); + sp = n->child->string; + + ep = strchr(sp, ' '); + if (NULL != ep) { + PAIR_CLASS_INIT(&tag[0], "ftype"); + t = print_otag(h, TAG_I, 1, tag); + + while (ep) { + sz = MIN((int)(ep - sp), BUFSIZ - 1); + (void)memcpy(nbuf, sp, (size_t)sz); + nbuf[sz] = '\0'; + print_text(h, nbuf); + sp = ++ep; + ep = strchr(sp, ' '); + } + print_tagq(h, t); + } + + PAIR_CLASS_INIT(&tag[0], "fname"); + + /* + * FIXME: only refer to IDs that we know exist. + */ + +#if 0 + if (MDOC_SYNPRETTY & n->flags) { + nbuf[0] = '\0'; + html_idcat(nbuf, sp, BUFSIZ); + PAIR_ID_INIT(&tag[1], nbuf); + } else { + strlcpy(nbuf, "#", BUFSIZ); + html_idcat(nbuf, sp, BUFSIZ); + PAIR_HREF_INIT(&tag[1], nbuf); + } +#endif + + t = print_otag(h, TAG_B, 1, tag); + + if (sp) { + strlcpy(nbuf, sp, BUFSIZ); + print_text(h, nbuf); + } + + print_tagq(h, t); + + h->flags |= HTML_NOSPACE; + print_text(h, "("); + h->flags |= HTML_NOSPACE; + + PAIR_CLASS_INIT(&tag[0], "farg"); + bufinit(h); + bufcat_style(h, "white-space", "nowrap"); + PAIR_STYLE_INIT(&tag[1], h); + + for (n = n->child->next; n; n = n->next) { + i = 1; + if (MDOC_SYNPRETTY & n->flags) + i = 2; + t = print_otag(h, TAG_I, i, tag); + print_text(h, n->string); + print_tagq(h, t); + if (n->next) { + h->flags |= HTML_NOSPACE; + print_text(h, ","); + } + } + + h->flags |= HTML_NOSPACE; + print_text(h, ")"); + + if (pretty) { + h->flags |= HTML_NOSPACE; + print_text(h, ";"); + } + + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_sm_pre(MDOC_ARGS) +{ + + assert(n->child && MDOC_TEXT == n->child->type); + if (0 == strcmp("on", n->child->string)) { + /* + * FIXME: no p->col to check. Thus, if we have + * .Bd -literal + * .Sm off + * 1 2 + * .Sm on + * 3 + * .Ed + * the "3" is preceded by a space. + */ + h->flags &= ~HTML_NOSPACE; + h->flags &= ~HTML_NONOSPACE; + } else + h->flags |= HTML_NONOSPACE; + + return(0); +} + +/* ARGSUSED */ +static int +mdoc_pp_pre(MDOC_ARGS) +{ + + print_otag(h, TAG_P, 0, NULL); + return(0); + +} + +/* ARGSUSED */ +static int +mdoc_sp_pre(MDOC_ARGS) +{ + struct roffsu su; + struct htmlpair tag; + + SCALE_VS_INIT(&su, 1); + + if (MDOC_sp == n->tok) { + if (NULL != (n = n->child)) + if ( ! a2roffsu(n->string, &su, SCALE_VS)) + SCALE_VS_INIT(&su, atoi(n->string)); + } else + su.scale = 0; + + bufinit(h); + bufcat_su(h, "height", &su); + PAIR_STYLE_INIT(&tag, h); + print_otag(h, TAG_DIV, 1, &tag); + + /* So the div isn't empty: */ + print_text(h, "\\~"); + + return(0); + +} + +/* ARGSUSED */ +static int +mdoc_lk_pre(MDOC_ARGS) +{ + struct htmlpair tag[2]; + + if (NULL == (n = n->child)) + return(0); + + assert(MDOC_TEXT == n->type); + + PAIR_CLASS_INIT(&tag[0], "link-ext"); + PAIR_HREF_INIT(&tag[1], n->string); + + print_otag(h, TAG_A, 2, tag); + + if (NULL == n->next) + print_text(h, n->string); + + for (n = n->next; n; n = n->next) + print_text(h, n->string); + + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_mt_pre(MDOC_ARGS) +{ + struct htmlpair tag[2]; + struct tag *t; + + PAIR_CLASS_INIT(&tag[0], "link-mail"); + + for (n = n->child; n; n = n->next) { + assert(MDOC_TEXT == n->type); + + bufinit(h); + bufcat(h, "mailto:"); + bufcat(h, n->string); + + PAIR_HREF_INIT(&tag[1], h->buf); + t = print_otag(h, TAG_A, 2, tag); + print_text(h, n->string); + print_tagq(h, t); + } + + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_fo_pre(MDOC_ARGS) +{ + struct htmlpair tag; + struct tag *t; + + if (MDOC_BODY == n->type) { + h->flags |= HTML_NOSPACE; + print_text(h, "("); + h->flags |= HTML_NOSPACE; + return(1); + } else if (MDOC_BLOCK == n->type) { + synopsis_pre(h, n); + return(1); + } + + /* XXX: we drop non-initial arguments as per groff. */ + + assert(n->child); + assert(n->child->string); + + PAIR_CLASS_INIT(&tag, "fname"); + t = print_otag(h, TAG_B, 1, &tag); + print_text(h, n->child->string); + print_tagq(h, t); + return(0); +} + + +/* ARGSUSED */ +static void +mdoc_fo_post(MDOC_ARGS) +{ + + if (MDOC_BODY != n->type) + return; + h->flags |= HTML_NOSPACE; + print_text(h, ")"); + h->flags |= HTML_NOSPACE; + print_text(h, ";"); +} + + +/* ARGSUSED */ +static int +mdoc_in_pre(MDOC_ARGS) +{ + struct tag *t; + struct htmlpair tag[2]; + int i; + + synopsis_pre(h, n); + + PAIR_CLASS_INIT(&tag[0], "includes"); + print_otag(h, TAG_B, 1, tag); + + /* + * The first argument of the `In' gets special treatment as + * being a linked value. Subsequent values are printed + * afterward. groff does similarly. This also handles the case + * of no children. + */ + + if (MDOC_SYNPRETTY & n->flags && MDOC_LINE & n->flags) + print_text(h, "#include"); + + print_text(h, "<"); + h->flags |= HTML_NOSPACE; + + if (NULL != (n = n->child)) { + assert(MDOC_TEXT == n->type); + + PAIR_CLASS_INIT(&tag[0], "link-includes"); + + i = 1; + if (h->base_includes) { + buffmt_includes(h, n->string); + PAIR_HREF_INIT(&tag[i], h->buf); + i++; + } + + t = print_otag(h, TAG_A, i, tag); + print_text(h, n->string); + print_tagq(h, t); + + n = n->next; + } + + h->flags |= HTML_NOSPACE; + print_text(h, ">"); + + for ( ; n; n = n->next) { + assert(MDOC_TEXT == n->type); + print_text(h, n->string); + } + + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_ic_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "cmd"); + print_otag(h, TAG_B, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_rv_pre(MDOC_ARGS) +{ + struct htmlpair tag; + struct tag *t; + int nchild; + + if (n->prev) + print_otag(h, TAG_BR, 0, NULL); + + PAIR_CLASS_INIT(&tag, "fname"); + + print_text(h, "The"); + + nchild = n->nchild; + for (n = n->child; n; n = n->next) { + assert(MDOC_TEXT == n->type); + + t = print_otag(h, TAG_B, 1, &tag); + print_text(h, n->string); + print_tagq(h, t); + + h->flags |= HTML_NOSPACE; + print_text(h, "()"); + + if (nchild > 2 && n->next) { + h->flags |= HTML_NOSPACE; + print_text(h, ","); + } + + if (n->next && NULL == n->next->next) + print_text(h, "and"); + } + + if (nchild > 1) + print_text(h, "functions return"); + else + print_text(h, "function returns"); + + print_text(h, "the value 0 if successful; otherwise the value " + "-1 is returned and the global variable"); + + PAIR_CLASS_INIT(&tag, "var"); + t = print_otag(h, TAG_B, 1, &tag); + print_text(h, "errno"); + print_tagq(h, t); + print_text(h, "is set to indicate the error."); + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_va_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "var"); + print_otag(h, TAG_B, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_ap_pre(MDOC_ARGS) +{ + + h->flags |= HTML_NOSPACE; + print_text(h, "\\(aq"); + h->flags |= HTML_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_bf_pre(MDOC_ARGS) +{ + struct htmlpair tag[2]; + struct roffsu su; + + if (MDOC_HEAD == n->type) + return(0); + else if (MDOC_BODY != n->type) + return(1); + + if (FONT_Em == n->norm->Bf.font) + PAIR_CLASS_INIT(&tag[0], "emph"); + else if (FONT_Sy == n->norm->Bf.font) + PAIR_CLASS_INIT(&tag[0], "symb"); + else if (FONT_Li == n->norm->Bf.font) + PAIR_CLASS_INIT(&tag[0], "lit"); + else + PAIR_CLASS_INIT(&tag[0], "none"); + + /* + * We want this to be inline-formatted, but needs to be div to + * accept block children. + */ + bufinit(h); + bufcat_style(h, "display", "inline"); + SCALE_HS_INIT(&su, 1); + /* Needs a left-margin for spacing. */ + bufcat_su(h, "margin-left", &su); + PAIR_STYLE_INIT(&tag[1], h); + print_otag(h, TAG_DIV, 2, tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_ms_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "symb"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_igndelim_pre(MDOC_ARGS) +{ + + h->flags |= HTML_IGNDELIM; + return(1); +} + + +/* ARGSUSED */ +static void +mdoc_pf_post(MDOC_ARGS) +{ + + h->flags |= HTML_NOSPACE; +} + + +/* ARGSUSED */ +static int +mdoc_rs_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + if (MDOC_BLOCK != n->type) + return(1); + + if (n->prev && SEC_SEE_ALSO == n->sec) + print_otag(h, TAG_P, 0, NULL); + + PAIR_CLASS_INIT(&tag, "ref"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + + +/* ARGSUSED */ +static int +mdoc_li_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "lit"); + print_otag(h, TAG_CODE, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_sy_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + PAIR_CLASS_INIT(&tag, "symb"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc_bt_pre(MDOC_ARGS) +{ + + print_text(h, "is currently in beta test."); + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_ud_pre(MDOC_ARGS) +{ + + print_text(h, "currently under development."); + return(0); +} + + +/* ARGSUSED */ +static int +mdoc_lb_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + if (SEC_LIBRARY == n->sec && MDOC_LINE & n->flags && n->prev) + print_otag(h, TAG_BR, 0, NULL); + + PAIR_CLASS_INIT(&tag, "lib"); + print_otag(h, TAG_SPAN, 1, &tag); + return(1); +} + + +/* ARGSUSED */ +static int +mdoc__x_pre(MDOC_ARGS) +{ + struct htmlpair tag[2]; + enum htmltag t; + + t = TAG_SPAN; + + switch (n->tok) { + case(MDOC__A): + PAIR_CLASS_INIT(&tag[0], "ref-auth"); + if (n->prev && MDOC__A == n->prev->tok) + if (NULL == n->next || MDOC__A != n->next->tok) + print_text(h, "and"); + break; + case(MDOC__B): + PAIR_CLASS_INIT(&tag[0], "ref-book"); + t = TAG_I; + break; + case(MDOC__C): + PAIR_CLASS_INIT(&tag[0], "ref-city"); + break; + case(MDOC__D): + PAIR_CLASS_INIT(&tag[0], "ref-date"); + break; + case(MDOC__I): + PAIR_CLASS_INIT(&tag[0], "ref-issue"); + t = TAG_I; + break; + case(MDOC__J): + PAIR_CLASS_INIT(&tag[0], "ref-jrnl"); + t = TAG_I; + break; + case(MDOC__N): + PAIR_CLASS_INIT(&tag[0], "ref-num"); + break; + case(MDOC__O): + PAIR_CLASS_INIT(&tag[0], "ref-opt"); + break; + case(MDOC__P): + PAIR_CLASS_INIT(&tag[0], "ref-page"); + break; + case(MDOC__Q): + PAIR_CLASS_INIT(&tag[0], "ref-corp"); + break; + case(MDOC__R): + PAIR_CLASS_INIT(&tag[0], "ref-rep"); + break; + case(MDOC__T): + PAIR_CLASS_INIT(&tag[0], "ref-title"); + break; + case(MDOC__U): + PAIR_CLASS_INIT(&tag[0], "link-ref"); + break; + case(MDOC__V): + PAIR_CLASS_INIT(&tag[0], "ref-vol"); + break; + default: + abort(); + /* NOTREACHED */ + } + + if (MDOC__U != n->tok) { + print_otag(h, t, 1, tag); + return(1); + } + + PAIR_HREF_INIT(&tag[1], n->child->string); + print_otag(h, TAG_A, 2, tag); + + return(1); +} + + +/* ARGSUSED */ +static void +mdoc__x_post(MDOC_ARGS) +{ + + if (MDOC__A == n->tok && n->next && MDOC__A == n->next->tok) + if (NULL == n->next->next || MDOC__A != n->next->next->tok) + if (NULL == n->prev || MDOC__A != n->prev->tok) + return; + + /* TODO: %U */ + + if (NULL == n->parent || MDOC_Rs != n->parent->tok) + return; + + h->flags |= HTML_NOSPACE; + print_text(h, n->next ? "," : "."); +} + + +/* ARGSUSED */ +static int +mdoc_bk_pre(MDOC_ARGS) +{ + + switch (n->type) { + case (MDOC_BLOCK): + break; + case (MDOC_HEAD): + return(0); + case (MDOC_BODY): + if (n->parent->args || 0 == n->prev->nchild) + h->flags |= HTML_PREKEEP; + break; + default: + abort(); + /* NOTREACHED */ + } + + return(1); +} + + +/* ARGSUSED */ +static void +mdoc_bk_post(MDOC_ARGS) +{ + + if (MDOC_BODY == n->type) + h->flags &= ~(HTML_KEEP | HTML_PREKEEP); +} + + +/* ARGSUSED */ +static int +mdoc_quote_pre(MDOC_ARGS) +{ + struct htmlpair tag; + + if (MDOC_BODY != n->type) + return(1); + + switch (n->tok) { + case (MDOC_Ao): + /* FALLTHROUGH */ + case (MDOC_Aq): + print_text(h, "\\(la"); + break; + case (MDOC_Bro): + /* FALLTHROUGH */ + case (MDOC_Brq): + print_text(h, "\\(lC"); + break; + case (MDOC_Bo): + /* FALLTHROUGH */ + case (MDOC_Bq): + print_text(h, "\\(lB"); + break; + case (MDOC_Oo): + /* FALLTHROUGH */ + case (MDOC_Op): + print_text(h, "\\(lB"); + h->flags |= HTML_NOSPACE; + PAIR_CLASS_INIT(&tag, "opt"); + print_otag(h, TAG_SPAN, 1, &tag); + break; + case (MDOC_Eo): + break; + case (MDOC_Do): + /* FALLTHROUGH */ + case (MDOC_Dq): + /* FALLTHROUGH */ + case (MDOC_Qo): + /* FALLTHROUGH */ + case (MDOC_Qq): + print_text(h, "\\(lq"); + break; + case (MDOC_Po): + /* FALLTHROUGH */ + case (MDOC_Pq): + print_text(h, "("); + break; + case (MDOC_Ql): + print_text(h, "\\(oq"); + h->flags |= HTML_NOSPACE; + PAIR_CLASS_INIT(&tag, "lit"); + print_otag(h, TAG_CODE, 1, &tag); + break; + case (MDOC_So): + /* FALLTHROUGH */ + case (MDOC_Sq): + print_text(h, "\\(oq"); + break; + default: + abort(); + /* NOTREACHED */ + } + + h->flags |= HTML_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static void +mdoc_quote_post(MDOC_ARGS) +{ + + if (MDOC_BODY != n->type) + return; + + h->flags |= HTML_NOSPACE; + + switch (n->tok) { + case (MDOC_Ao): + /* FALLTHROUGH */ + case (MDOC_Aq): + print_text(h, "\\(ra"); + break; + case (MDOC_Bro): + /* FALLTHROUGH */ + case (MDOC_Brq): + print_text(h, "\\(rC"); + break; + case (MDOC_Oo): + /* FALLTHROUGH */ + case (MDOC_Op): + /* FALLTHROUGH */ + case (MDOC_Bo): + /* FALLTHROUGH */ + case (MDOC_Bq): + print_text(h, "\\(rB"); + break; + case (MDOC_Eo): + break; + case (MDOC_Qo): + /* FALLTHROUGH */ + case (MDOC_Qq): + /* FALLTHROUGH */ + case (MDOC_Do): + /* FALLTHROUGH */ + case (MDOC_Dq): + print_text(h, "\\(rq"); + break; + case (MDOC_Po): + /* FALLTHROUGH */ + case (MDOC_Pq): + print_text(h, ")"); + break; + case (MDOC_Ql): + /* FALLTHROUGH */ + case (MDOC_So): + /* FALLTHROUGH */ + case (MDOC_Sq): + print_text(h, "\\(aq"); + break; + default: + abort(); + /* NOTREACHED */ + } +} + + diff --git a/mdoc_macro.c b/mdoc_macro.c new file mode 100644 index 000000000000..11d147399eef --- /dev/null +++ b/mdoc_macro.c @@ -0,0 +1,1787 @@ +/* $Id: mdoc_macro.c,v 1.115 2012/01/05 00:43:51 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * Copyright (c) 2010 Ingo Schwarze <schwarze@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <assert.h> +#include <ctype.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <time.h> + +#include "mdoc.h" +#include "mandoc.h" +#include "libmdoc.h" +#include "libmandoc.h" + +enum rew { /* see rew_dohalt() */ + REWIND_NONE, + REWIND_THIS, + REWIND_MORE, + REWIND_FORCE, + REWIND_LATER, + REWIND_ERROR +}; + +static int blk_full(MACRO_PROT_ARGS); +static int blk_exp_close(MACRO_PROT_ARGS); +static int blk_part_exp(MACRO_PROT_ARGS); +static int blk_part_imp(MACRO_PROT_ARGS); +static int ctx_synopsis(MACRO_PROT_ARGS); +static int in_line_eoln(MACRO_PROT_ARGS); +static int in_line_argn(MACRO_PROT_ARGS); +static int in_line(MACRO_PROT_ARGS); +static int obsolete(MACRO_PROT_ARGS); +static int phrase_ta(MACRO_PROT_ARGS); + +static int dword(struct mdoc *, int, int, + const char *, enum mdelim); +static int append_delims(struct mdoc *, + int, int *, char *); +static enum mdoct lookup(enum mdoct, const char *); +static enum mdoct lookup_raw(const char *); +static int make_pending(struct mdoc_node *, enum mdoct, + struct mdoc *, int, int); +static int phrase(struct mdoc *, int, int, char *); +static enum mdoct rew_alt(enum mdoct); +static enum rew rew_dohalt(enum mdoct, enum mdoc_type, + const struct mdoc_node *); +static int rew_elem(struct mdoc *, enum mdoct); +static int rew_last(struct mdoc *, + const struct mdoc_node *); +static int rew_sub(enum mdoc_type, struct mdoc *, + enum mdoct, int, int); + +const struct mdoc_macro __mdoc_macros[MDOC_MAX] = { + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Ap */ + { in_line_eoln, MDOC_PROLOGUE }, /* Dd */ + { in_line_eoln, MDOC_PROLOGUE }, /* Dt */ + { in_line_eoln, MDOC_PROLOGUE }, /* Os */ + { blk_full, MDOC_PARSED }, /* Sh */ + { blk_full, MDOC_PARSED }, /* Ss */ + { in_line_eoln, 0 }, /* Pp */ + { blk_part_imp, MDOC_PARSED }, /* D1 */ + { blk_part_imp, MDOC_PARSED }, /* Dl */ + { blk_full, MDOC_EXPLICIT }, /* Bd */ + { blk_exp_close, MDOC_EXPLICIT }, /* Ed */ + { blk_full, MDOC_EXPLICIT }, /* Bl */ + { blk_exp_close, MDOC_EXPLICIT }, /* El */ + { blk_full, MDOC_PARSED }, /* It */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ad */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* An */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ar */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Cd */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Cm */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Dv */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Er */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ev */ + { in_line_eoln, 0 }, /* Ex */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Fa */ + { in_line_eoln, 0 }, /* Fd */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Fl */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Fn */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ft */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ic */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* In */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Li */ + { blk_full, 0 }, /* Nd */ + { ctx_synopsis, MDOC_CALLABLE | MDOC_PARSED }, /* Nm */ + { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Op */ + { obsolete, 0 }, /* Ot */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Pa */ + { in_line_eoln, 0 }, /* Rv */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* St */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Va */ + { ctx_synopsis, MDOC_CALLABLE | MDOC_PARSED }, /* Vt */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Xr */ + { in_line_eoln, 0 }, /* %A */ + { in_line_eoln, 0 }, /* %B */ + { in_line_eoln, 0 }, /* %D */ + { in_line_eoln, 0 }, /* %I */ + { in_line_eoln, 0 }, /* %J */ + { in_line_eoln, 0 }, /* %N */ + { in_line_eoln, 0 }, /* %O */ + { in_line_eoln, 0 }, /* %P */ + { in_line_eoln, 0 }, /* %R */ + { in_line_eoln, 0 }, /* %T */ + { in_line_eoln, 0 }, /* %V */ + { blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Ac */ + { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Ao */ + { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Aq */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* At */ + { blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Bc */ + { blk_full, MDOC_EXPLICIT }, /* Bf */ + { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Bo */ + { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Bq */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Bsx */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Bx */ + { in_line_eoln, 0 }, /* Db */ + { blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Dc */ + { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Do */ + { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Dq */ + { blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Ec */ + { blk_exp_close, MDOC_EXPLICIT }, /* Ef */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Em */ + { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Eo */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Fx */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ms */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED | MDOC_IGNDELIM }, /* No */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED | MDOC_IGNDELIM }, /* Ns */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Nx */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Ox */ + { blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Pc */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED | MDOC_IGNDELIM }, /* Pf */ + { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Po */ + { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Pq */ + { blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Qc */ + { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Ql */ + { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Qo */ + { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Qq */ + { blk_exp_close, MDOC_EXPLICIT }, /* Re */ + { blk_full, MDOC_EXPLICIT }, /* Rs */ + { blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Sc */ + { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* So */ + { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Sq */ + { in_line_eoln, 0 }, /* Sm */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Sx */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Sy */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Tn */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Ux */ + { blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Xc */ + { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Xo */ + { blk_full, MDOC_EXPLICIT | MDOC_CALLABLE }, /* Fo */ + { blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Fc */ + { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Oo */ + { blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Oc */ + { blk_full, MDOC_EXPLICIT }, /* Bk */ + { blk_exp_close, MDOC_EXPLICIT }, /* Ek */ + { in_line_eoln, 0 }, /* Bt */ + { in_line_eoln, 0 }, /* Hf */ + { obsolete, 0 }, /* Fr */ + { in_line_eoln, 0 }, /* Ud */ + { in_line, 0 }, /* Lb */ + { in_line_eoln, 0 }, /* Lp */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Lk */ + { in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Mt */ + { blk_part_imp, MDOC_CALLABLE | MDOC_PARSED }, /* Brq */ + { blk_part_exp, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Bro */ + { blk_exp_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Brc */ + { in_line_eoln, 0 }, /* %C */ + { obsolete, 0 }, /* Es */ + { obsolete, 0 }, /* En */ + { in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Dx */ + { in_line_eoln, 0 }, /* %Q */ + { in_line_eoln, 0 }, /* br */ + { in_line_eoln, 0 }, /* sp */ + { in_line_eoln, 0 }, /* %U */ + { phrase_ta, MDOC_CALLABLE | MDOC_PARSED }, /* Ta */ +}; + +const struct mdoc_macro * const mdoc_macros = __mdoc_macros; + + +/* + * This is called at the end of parsing. It must traverse up the tree, + * closing out open [implicit] scopes. Obviously, open explicit scopes + * are errors. + */ +int +mdoc_macroend(struct mdoc *m) +{ + struct mdoc_node *n; + + /* Scan for open explicit scopes. */ + + n = MDOC_VALID & m->last->flags ? m->last->parent : m->last; + + for ( ; n; n = n->parent) + if (MDOC_BLOCK == n->type && + MDOC_EXPLICIT & mdoc_macros[n->tok].flags) + mdoc_nmsg(m, n, MANDOCERR_SCOPEEXIT); + + /* Rewind to the first. */ + + return(rew_last(m, m->first)); +} + + +/* + * Look up a macro from within a subsequent context. + */ +static enum mdoct +lookup(enum mdoct from, const char *p) +{ + + if ( ! (MDOC_PARSED & mdoc_macros[from].flags)) + return(MDOC_MAX); + return(lookup_raw(p)); +} + + +/* + * Lookup a macro following the initial line macro. + */ +static enum mdoct +lookup_raw(const char *p) +{ + enum mdoct res; + + if (MDOC_MAX == (res = mdoc_hash_find(p))) + return(MDOC_MAX); + if (MDOC_CALLABLE & mdoc_macros[res].flags) + return(res); + return(MDOC_MAX); +} + + +static int +rew_last(struct mdoc *mdoc, const struct mdoc_node *to) +{ + struct mdoc_node *n, *np; + + assert(to); + mdoc->next = MDOC_NEXT_SIBLING; + + /* LINTED */ + while (mdoc->last != to) { + /* + * Save the parent here, because we may delete the + * m->last node in the post-validation phase and reset + * it to m->last->parent, causing a step in the closing + * out to be lost. + */ + np = mdoc->last->parent; + if ( ! mdoc_valid_post(mdoc)) + return(0); + n = mdoc->last; + mdoc->last = np; + assert(mdoc->last); + mdoc->last->last = n; + } + + return(mdoc_valid_post(mdoc)); +} + + +/* + * For a block closing macro, return the corresponding opening one. + * Otherwise, return the macro itself. + */ +static enum mdoct +rew_alt(enum mdoct tok) +{ + switch (tok) { + case (MDOC_Ac): + return(MDOC_Ao); + case (MDOC_Bc): + return(MDOC_Bo); + case (MDOC_Brc): + return(MDOC_Bro); + case (MDOC_Dc): + return(MDOC_Do); + case (MDOC_Ec): + return(MDOC_Eo); + case (MDOC_Ed): + return(MDOC_Bd); + case (MDOC_Ef): + return(MDOC_Bf); + case (MDOC_Ek): + return(MDOC_Bk); + case (MDOC_El): + return(MDOC_Bl); + case (MDOC_Fc): + return(MDOC_Fo); + case (MDOC_Oc): + return(MDOC_Oo); + case (MDOC_Pc): + return(MDOC_Po); + case (MDOC_Qc): + return(MDOC_Qo); + case (MDOC_Re): + return(MDOC_Rs); + case (MDOC_Sc): + return(MDOC_So); + case (MDOC_Xc): + return(MDOC_Xo); + default: + return(tok); + } + /* NOTREACHED */ +} + + +/* + * Rewinding to tok, how do we have to handle *p? + * REWIND_NONE: *p would delimit tok, but no tok scope is open + * inside *p, so there is no need to rewind anything at all. + * REWIND_THIS: *p matches tok, so rewind *p and nothing else. + * REWIND_MORE: *p is implicit, rewind it and keep searching for tok. + * REWIND_FORCE: *p is explicit, but tok is full, force rewinding *p. + * REWIND_LATER: *p is explicit and still open, postpone rewinding. + * REWIND_ERROR: No tok block is open at all. + */ +static enum rew +rew_dohalt(enum mdoct tok, enum mdoc_type type, + const struct mdoc_node *p) +{ + + /* + * No matching token, no delimiting block, no broken block. + * This can happen when full implicit macros are called for + * the first time but try to rewind their previous + * instance anyway. + */ + if (MDOC_ROOT == p->type) + return(MDOC_BLOCK == type && + MDOC_EXPLICIT & mdoc_macros[tok].flags ? + REWIND_ERROR : REWIND_NONE); + + /* + * When starting to rewind, skip plain text + * and nodes that have already been rewound. + */ + if (MDOC_TEXT == p->type || MDOC_VALID & p->flags) + return(REWIND_MORE); + + /* + * The easiest case: Found a matching token. + * This applies to both blocks and elements. + */ + tok = rew_alt(tok); + if (tok == p->tok) + return(p->end ? REWIND_NONE : + type == p->type ? REWIND_THIS : REWIND_MORE); + + /* + * While elements do require rewinding for themselves, + * they never affect rewinding of other nodes. + */ + if (MDOC_ELEM == p->type) + return(REWIND_MORE); + + /* + * Blocks delimited by our target token get REWIND_MORE. + * Blocks delimiting our target token get REWIND_NONE. + */ + switch (tok) { + case (MDOC_Bl): + if (MDOC_It == p->tok) + return(REWIND_MORE); + break; + case (MDOC_It): + if (MDOC_BODY == p->type && MDOC_Bl == p->tok) + return(REWIND_NONE); + break; + /* + * XXX Badly nested block handling still fails badly + * when one block is breaking two blocks of the same type. + * This is an incomplete and extremely ugly workaround, + * required to let the OpenBSD tree build. + */ + case (MDOC_Oo): + if (MDOC_Op == p->tok) + return(REWIND_MORE); + break; + case (MDOC_Nm): + return(REWIND_NONE); + case (MDOC_Nd): + /* FALLTHROUGH */ + case (MDOC_Ss): + if (MDOC_BODY == p->type && MDOC_Sh == p->tok) + return(REWIND_NONE); + /* FALLTHROUGH */ + case (MDOC_Sh): + if (MDOC_Nd == p->tok || MDOC_Ss == p->tok || + MDOC_Sh == p->tok) + return(REWIND_MORE); + break; + default: + break; + } + + /* + * Default block rewinding rules. + * In particular, always skip block end markers, + * and let all blocks rewind Nm children. + */ + if (ENDBODY_NOT != p->end || MDOC_Nm == p->tok || + (MDOC_BLOCK == p->type && + ! (MDOC_EXPLICIT & mdoc_macros[tok].flags))) + return(REWIND_MORE); + + /* + * By default, closing out full blocks + * forces closing of broken explicit blocks, + * while closing out partial blocks + * allows delayed rewinding by default. + */ + return (&blk_full == mdoc_macros[tok].fp ? + REWIND_FORCE : REWIND_LATER); +} + + +static int +rew_elem(struct mdoc *mdoc, enum mdoct tok) +{ + struct mdoc_node *n; + + n = mdoc->last; + if (MDOC_ELEM != n->type) + n = n->parent; + assert(MDOC_ELEM == n->type); + assert(tok == n->tok); + + return(rew_last(mdoc, n)); +} + + +/* + * We are trying to close a block identified by tok, + * but the child block *broken is still open. + * Thus, postpone closing the tok block + * until the rew_sub call closing *broken. + */ +static int +make_pending(struct mdoc_node *broken, enum mdoct tok, + struct mdoc *m, int line, int ppos) +{ + struct mdoc_node *breaker; + + /* + * Iterate backwards, searching for the block matching tok, + * that is, the block breaking the *broken block. + */ + for (breaker = broken->parent; breaker; breaker = breaker->parent) { + + /* + * If the *broken block had already been broken before + * and we encounter its breaker, make the tok block + * pending on the inner breaker. + * Graphically, "[A breaker=[B broken=[C->B B] tok=A] C]" + * becomes "[A broken=[B [C->B B] tok=A] C]" + * and finally "[A [B->A [C->B B] A] C]". + */ + if (breaker == broken->pending) { + broken = breaker; + continue; + } + + if (REWIND_THIS != rew_dohalt(tok, MDOC_BLOCK, breaker)) + continue; + if (MDOC_BODY == broken->type) + broken = broken->parent; + + /* + * Found the breaker. + * If another, outer breaker is already pending on + * the *broken block, we must not clobber the link + * to the outer breaker, but make it pending on the + * new, now inner breaker. + * Graphically, "[A breaker=[B broken=[C->A A] tok=B] C]" + * becomes "[A breaker=[B->A broken=[C A] tok=B] C]" + * and finally "[A [B->A [C->B A] B] C]". + */ + if (broken->pending) { + struct mdoc_node *taker; + + /* + * If the breaker had also been broken before, + * it cannot take on the outer breaker itself, + * but must hand it on to its own breakers. + * Graphically, this is the following situation: + * "[A [B breaker=[C->B B] broken=[D->A A] tok=C] D]" + * "[A taker=[B->A breaker=[C->B B] [D->C A] C] D]" + */ + taker = breaker; + while (taker->pending) + taker = taker->pending; + taker->pending = broken->pending; + } + broken->pending = breaker; + mandoc_vmsg(MANDOCERR_SCOPENEST, m->parse, line, ppos, + "%s breaks %s", mdoc_macronames[tok], + mdoc_macronames[broken->tok]); + return(1); + } + + /* + * Found no matching block for tok. + * Are you trying to close a block that is not open? + */ + return(0); +} + + +static int +rew_sub(enum mdoc_type t, struct mdoc *m, + enum mdoct tok, int line, int ppos) +{ + struct mdoc_node *n; + + n = m->last; + while (n) { + switch (rew_dohalt(tok, t, n)) { + case (REWIND_NONE): + return(1); + case (REWIND_THIS): + break; + case (REWIND_FORCE): + mandoc_vmsg(MANDOCERR_SCOPEBROKEN, m->parse, + line, ppos, "%s breaks %s", + mdoc_macronames[tok], + mdoc_macronames[n->tok]); + /* FALLTHROUGH */ + case (REWIND_MORE): + n = n->parent; + continue; + case (REWIND_LATER): + if (make_pending(n, tok, m, line, ppos) || + MDOC_BLOCK != t) + return(1); + /* FALLTHROUGH */ + case (REWIND_ERROR): + mdoc_pmsg(m, line, ppos, MANDOCERR_NOSCOPE); + return(1); + } + break; + } + + assert(n); + if ( ! rew_last(m, n)) + return(0); + + /* + * The current block extends an enclosing block. + * Now that the current block ends, close the enclosing block, too. + */ + while (NULL != (n = n->pending)) { + if ( ! rew_last(m, n)) + return(0); + if (MDOC_HEAD == n->type && + ! mdoc_body_alloc(m, n->line, n->pos, n->tok)) + return(0); + } + + return(1); +} + +/* + * Allocate a word and check whether it's punctuation or not. + * Punctuation consists of those tokens found in mdoc_isdelim(). + */ +static int +dword(struct mdoc *m, int line, + int col, const char *p, enum mdelim d) +{ + + if (DELIM_MAX == d) + d = mdoc_isdelim(p); + + if ( ! mdoc_word_alloc(m, line, col, p)) + return(0); + + if (DELIM_OPEN == d) + m->last->flags |= MDOC_DELIMO; + + /* + * Closing delimiters only suppress the preceding space + * when they follow something, not when they start a new + * block or element, and not when they follow `No'. + * + * XXX Explicitly special-casing MDOC_No here feels + * like a layering violation. Find a better way + * and solve this in the code related to `No'! + */ + + else if (DELIM_CLOSE == d && m->last->prev && + m->last->prev->tok != MDOC_No) + m->last->flags |= MDOC_DELIMC; + + return(1); +} + +static int +append_delims(struct mdoc *m, int line, int *pos, char *buf) +{ + int la; + enum margserr ac; + char *p; + + if ('\0' == buf[*pos]) + return(1); + + for (;;) { + la = *pos; + ac = mdoc_zargs(m, line, pos, buf, &p); + + if (ARGS_ERROR == ac) + return(0); + else if (ARGS_EOLN == ac) + break; + + dword(m, line, la, p, DELIM_MAX); + + /* + * If we encounter end-of-sentence symbols, then trigger + * the double-space. + * + * XXX: it's easy to allow this to propagate outward to + * the last symbol, such that `. )' will cause the + * correct double-spacing. However, (1) groff isn't + * smart enough to do this and (2) it would require + * knowing which symbols break this behaviour, for + * example, `. ;' shouldn't propagate the double-space. + */ + if (mandoc_eos(p, strlen(p), 0)) + m->last->flags |= MDOC_EOS; + } + + return(1); +} + + +/* + * Close out block partial/full explicit. + */ +static int +blk_exp_close(MACRO_PROT_ARGS) +{ + struct mdoc_node *body; /* Our own body. */ + struct mdoc_node *later; /* A sub-block starting later. */ + struct mdoc_node *n; /* For searching backwards. */ + + int j, lastarg, maxargs, flushed, nl; + enum margserr ac; + enum mdoct atok, ntok; + char *p; + + nl = MDOC_NEWLINE & m->flags; + + switch (tok) { + case (MDOC_Ec): + maxargs = 1; + break; + default: + maxargs = 0; + break; + } + + /* + * Search backwards for beginnings of blocks, + * both of our own and of pending sub-blocks. + */ + atok = rew_alt(tok); + body = later = NULL; + for (n = m->last; n; n = n->parent) { + if (MDOC_VALID & n->flags) + continue; + + /* Remember the start of our own body. */ + if (MDOC_BODY == n->type && atok == n->tok) { + if (ENDBODY_NOT == n->end) + body = n; + continue; + } + + if (MDOC_BLOCK != n->type || MDOC_Nm == n->tok) + continue; + if (atok == n->tok) { + assert(body); + + /* + * Found the start of our own block. + * When there is no pending sub block, + * just proceed to closing out. + */ + if (NULL == later) + break; + + /* + * When there is a pending sub block, + * postpone closing out the current block + * until the rew_sub() closing out the sub-block. + */ + make_pending(later, tok, m, line, ppos); + + /* + * Mark the place where the formatting - but not + * the scope - of the current block ends. + */ + if ( ! mdoc_endbody_alloc(m, line, ppos, + atok, body, ENDBODY_SPACE)) + return(0); + break; + } + + /* + * When finding an open sub block, remember the last + * open explicit block, or, in case there are only + * implicit ones, the first open implicit block. + */ + if (later && + MDOC_EXPLICIT & mdoc_macros[later->tok].flags) + continue; + if (MDOC_CALLABLE & mdoc_macros[n->tok].flags) + later = n; + } + + if ( ! (MDOC_CALLABLE & mdoc_macros[tok].flags)) { + /* FIXME: do this in validate */ + if (buf[*pos]) + mdoc_pmsg(m, line, ppos, MANDOCERR_ARGSLOST); + + if ( ! rew_sub(MDOC_BODY, m, tok, line, ppos)) + return(0); + return(rew_sub(MDOC_BLOCK, m, tok, line, ppos)); + } + + if ( ! rew_sub(MDOC_BODY, m, tok, line, ppos)) + return(0); + + if (NULL == later && maxargs > 0) + if ( ! mdoc_tail_alloc(m, line, ppos, rew_alt(tok))) + return(0); + + for (flushed = j = 0; ; j++) { + lastarg = *pos; + + if (j == maxargs && ! flushed) { + if ( ! rew_sub(MDOC_BLOCK, m, tok, line, ppos)) + return(0); + flushed = 1; + } + + ac = mdoc_args(m, line, pos, buf, tok, &p); + + if (ARGS_ERROR == ac) + return(0); + if (ARGS_PUNCT == ac) + break; + if (ARGS_EOLN == ac) + break; + + ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p); + + if (MDOC_MAX == ntok) { + if ( ! dword(m, line, lastarg, p, DELIM_MAX)) + return(0); + continue; + } + + if ( ! flushed) { + if ( ! rew_sub(MDOC_BLOCK, m, tok, line, ppos)) + return(0); + flushed = 1; + } + if ( ! mdoc_macro(m, ntok, line, lastarg, pos, buf)) + return(0); + break; + } + + if ( ! flushed && ! rew_sub(MDOC_BLOCK, m, tok, line, ppos)) + return(0); + + if ( ! nl) + return(1); + return(append_delims(m, line, pos, buf)); +} + + +static int +in_line(MACRO_PROT_ARGS) +{ + int la, scope, cnt, nc, nl; + enum margverr av; + enum mdoct ntok; + enum margserr ac; + enum mdelim d; + struct mdoc_arg *arg; + char *p; + + nl = MDOC_NEWLINE & m->flags; + + /* + * Whether we allow ignored elements (those without content, + * usually because of reserved words) to squeak by. + */ + + switch (tok) { + case (MDOC_An): + /* FALLTHROUGH */ + case (MDOC_Ar): + /* FALLTHROUGH */ + case (MDOC_Fl): + /* FALLTHROUGH */ + case (MDOC_Mt): + /* FALLTHROUGH */ + case (MDOC_Nm): + /* FALLTHROUGH */ + case (MDOC_Pa): + nc = 1; + break; + default: + nc = 0; + break; + } + + for (arg = NULL;; ) { + la = *pos; + av = mdoc_argv(m, line, tok, &arg, pos, buf); + + if (ARGV_WORD == av) { + *pos = la; + break; + } + if (ARGV_EOLN == av) + break; + if (ARGV_ARG == av) + continue; + + mdoc_argv_free(arg); + return(0); + } + + for (cnt = scope = 0;; ) { + la = *pos; + ac = mdoc_args(m, line, pos, buf, tok, &p); + + if (ARGS_ERROR == ac) + return(0); + if (ARGS_EOLN == ac) + break; + if (ARGS_PUNCT == ac) + break; + + ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p); + + /* + * In this case, we've located a submacro and must + * execute it. Close out scope, if open. If no + * elements have been generated, either create one (nc) + * or raise a warning. + */ + + if (MDOC_MAX != ntok) { + if (scope && ! rew_elem(m, tok)) + return(0); + if (nc && 0 == cnt) { + if ( ! mdoc_elem_alloc(m, line, ppos, tok, arg)) + return(0); + if ( ! rew_last(m, m->last)) + return(0); + } else if ( ! nc && 0 == cnt) { + mdoc_argv_free(arg); + mdoc_pmsg(m, line, ppos, MANDOCERR_MACROEMPTY); + } + + if ( ! mdoc_macro(m, ntok, line, la, pos, buf)) + return(0); + if ( ! nl) + return(1); + return(append_delims(m, line, pos, buf)); + } + + /* + * Non-quote-enclosed punctuation. Set up our scope, if + * a word; rewind the scope, if a delimiter; then append + * the word. + */ + + d = ARGS_QWORD == ac ? DELIM_NONE : mdoc_isdelim(p); + + if (DELIM_NONE != d) { + /* + * If we encounter closing punctuation, no word + * has been omitted, no scope is open, and we're + * allowed to have an empty element, then start + * a new scope. `Ar', `Fl', and `Li', only do + * this once per invocation. There may be more + * of these (all of them?). + */ + if (0 == cnt && (nc || MDOC_Li == tok) && + DELIM_CLOSE == d && ! scope) { + if ( ! mdoc_elem_alloc(m, line, ppos, tok, arg)) + return(0); + if (MDOC_Ar == tok || MDOC_Li == tok || + MDOC_Fl == tok) + cnt++; + scope = 1; + } + /* + * Close out our scope, if one is open, before + * any punctuation. + */ + if (scope && ! rew_elem(m, tok)) + return(0); + scope = 0; + } else if ( ! scope) { + if ( ! mdoc_elem_alloc(m, line, ppos, tok, arg)) + return(0); + scope = 1; + } + + if (DELIM_NONE == d) + cnt++; + + if ( ! dword(m, line, la, p, d)) + return(0); + + /* + * `Fl' macros have their scope re-opened with each new + * word so that the `-' can be added to each one without + * having to parse out spaces. + */ + if (scope && MDOC_Fl == tok) { + if ( ! rew_elem(m, tok)) + return(0); + scope = 0; + } + } + + if (scope && ! rew_elem(m, tok)) + return(0); + + /* + * If no elements have been collected and we're allowed to have + * empties (nc), open a scope and close it out. Otherwise, + * raise a warning. + */ + + if (nc && 0 == cnt) { + if ( ! mdoc_elem_alloc(m, line, ppos, tok, arg)) + return(0); + if ( ! rew_last(m, m->last)) + return(0); + } else if ( ! nc && 0 == cnt) { + mdoc_argv_free(arg); + mdoc_pmsg(m, line, ppos, MANDOCERR_MACROEMPTY); + } + + if ( ! nl) + return(1); + return(append_delims(m, line, pos, buf)); +} + + +static int +blk_full(MACRO_PROT_ARGS) +{ + int la, nl, nparsed; + struct mdoc_arg *arg; + struct mdoc_node *head; /* save of head macro */ + struct mdoc_node *body; /* save of body macro */ + struct mdoc_node *n; + enum mdoc_type mtt; + enum mdoct ntok; + enum margserr ac, lac; + enum margverr av; + char *p; + + nl = MDOC_NEWLINE & m->flags; + + /* Close out prior implicit scope. */ + + if ( ! (MDOC_EXPLICIT & mdoc_macros[tok].flags)) { + if ( ! rew_sub(MDOC_BODY, m, tok, line, ppos)) + return(0); + if ( ! rew_sub(MDOC_BLOCK, m, tok, line, ppos)) + return(0); + } + + /* + * This routine accommodates implicitly- and explicitly-scoped + * macro openings. Implicit ones first close out prior scope + * (seen above). Delay opening the head until necessary to + * allow leading punctuation to print. Special consideration + * for `It -column', which has phrase-part syntax instead of + * regular child nodes. + */ + + for (arg = NULL;; ) { + la = *pos; + av = mdoc_argv(m, line, tok, &arg, pos, buf); + + if (ARGV_WORD == av) { + *pos = la; + break; + } + + if (ARGV_EOLN == av) + break; + if (ARGV_ARG == av) + continue; + + mdoc_argv_free(arg); + return(0); + } + + if ( ! mdoc_block_alloc(m, line, ppos, tok, arg)) + return(0); + + head = body = NULL; + + /* + * Exception: Heads of `It' macros in `-diag' lists are not + * parsed, even though `It' macros in general are parsed. + */ + nparsed = MDOC_It == tok && + MDOC_Bl == m->last->parent->tok && + LIST_diag == m->last->parent->norm->Bl.type; + + /* + * The `Nd' macro has all arguments in its body: it's a hybrid + * of block partial-explicit and full-implicit. Stupid. + */ + + if (MDOC_Nd == tok) { + if ( ! mdoc_head_alloc(m, line, ppos, tok)) + return(0); + head = m->last; + if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos)) + return(0); + if ( ! mdoc_body_alloc(m, line, ppos, tok)) + return(0); + body = m->last; + } + + ac = ARGS_ERROR; + + for ( ; ; ) { + la = *pos; + /* Initialise last-phrase-type with ARGS_PEND. */ + lac = ARGS_ERROR == ac ? ARGS_PEND : ac; + ac = mdoc_args(m, line, pos, buf, tok, &p); + + if (ARGS_PUNCT == ac) + break; + + if (ARGS_ERROR == ac) + return(0); + + if (ARGS_EOLN == ac) { + if (ARGS_PPHRASE != lac && ARGS_PHRASE != lac) + break; + /* + * This is necessary: if the last token on a + * line is a `Ta' or tab, then we'll get + * ARGS_EOLN, so we must be smart enough to + * reopen our scope if the last parse was a + * phrase or partial phrase. + */ + if ( ! rew_sub(MDOC_BODY, m, tok, line, ppos)) + return(0); + if ( ! mdoc_body_alloc(m, line, ppos, tok)) + return(0); + body = m->last; + break; + } + + /* + * Emit leading punctuation (i.e., punctuation before + * the MDOC_HEAD) for non-phrase types. + */ + + if (NULL == head && + ARGS_PEND != ac && + ARGS_PHRASE != ac && + ARGS_PPHRASE != ac && + ARGS_QWORD != ac && + DELIM_OPEN == mdoc_isdelim(p)) { + if ( ! dword(m, line, la, p, DELIM_OPEN)) + return(0); + continue; + } + + /* Open a head if one hasn't been opened. */ + + if (NULL == head) { + if ( ! mdoc_head_alloc(m, line, ppos, tok)) + return(0); + head = m->last; + } + + if (ARGS_PHRASE == ac || + ARGS_PEND == ac || + ARGS_PPHRASE == ac) { + /* + * If we haven't opened a body yet, rewind the + * head; if we have, rewind that instead. + */ + + mtt = body ? MDOC_BODY : MDOC_HEAD; + if ( ! rew_sub(mtt, m, tok, line, ppos)) + return(0); + + /* Then allocate our body context. */ + + if ( ! mdoc_body_alloc(m, line, ppos, tok)) + return(0); + body = m->last; + + /* + * Process phrases: set whether we're in a + * partial-phrase (this effects line handling) + * then call down into the phrase parser. + */ + + if (ARGS_PPHRASE == ac) + m->flags |= MDOC_PPHRASE; + if (ARGS_PEND == ac && ARGS_PPHRASE == lac) + m->flags |= MDOC_PPHRASE; + + if ( ! phrase(m, line, la, buf)) + return(0); + + m->flags &= ~MDOC_PPHRASE; + continue; + } + + ntok = nparsed || ARGS_QWORD == ac ? + MDOC_MAX : lookup(tok, p); + + if (MDOC_MAX == ntok) { + if ( ! dword(m, line, la, p, DELIM_MAX)) + return(0); + continue; + } + + if ( ! mdoc_macro(m, ntok, line, la, pos, buf)) + return(0); + break; + } + + if (NULL == head) { + if ( ! mdoc_head_alloc(m, line, ppos, tok)) + return(0); + head = m->last; + } + + if (nl && ! append_delims(m, line, pos, buf)) + return(0); + + /* If we've already opened our body, exit now. */ + + if (NULL != body) + goto out; + + /* + * If there is an open (i.e., unvalidated) sub-block requiring + * explicit close-out, postpone switching the current block from + * head to body until the rew_sub() call closing out that + * sub-block. + */ + for (n = m->last; n && n != head; n = n->parent) { + if (MDOC_BLOCK == n->type && + MDOC_EXPLICIT & mdoc_macros[n->tok].flags && + ! (MDOC_VALID & n->flags)) { + n->pending = head; + return(1); + } + } + + /* Close out scopes to remain in a consistent state. */ + + if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos)) + return(0); + if ( ! mdoc_body_alloc(m, line, ppos, tok)) + return(0); + +out: + if ( ! (MDOC_FREECOL & m->flags)) + return(1); + + if ( ! rew_sub(MDOC_BODY, m, tok, line, ppos)) + return(0); + if ( ! rew_sub(MDOC_BLOCK, m, tok, line, ppos)) + return(0); + + m->flags &= ~MDOC_FREECOL; + return(1); +} + + +static int +blk_part_imp(MACRO_PROT_ARGS) +{ + int la, nl; + enum mdoct ntok; + enum margserr ac; + char *p; + struct mdoc_node *blk; /* saved block context */ + struct mdoc_node *body; /* saved body context */ + struct mdoc_node *n; + + nl = MDOC_NEWLINE & m->flags; + + /* + * A macro that spans to the end of the line. This is generally + * (but not necessarily) called as the first macro. The block + * has a head as the immediate child, which is always empty, + * followed by zero or more opening punctuation nodes, then the + * body (which may be empty, depending on the macro), then zero + * or more closing punctuation nodes. + */ + + if ( ! mdoc_block_alloc(m, line, ppos, tok, NULL)) + return(0); + + blk = m->last; + + if ( ! mdoc_head_alloc(m, line, ppos, tok)) + return(0); + if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos)) + return(0); + + /* + * Open the body scope "on-demand", that is, after we've + * processed all our the leading delimiters (open parenthesis, + * etc.). + */ + + for (body = NULL; ; ) { + la = *pos; + ac = mdoc_args(m, line, pos, buf, tok, &p); + + if (ARGS_ERROR == ac) + return(0); + if (ARGS_EOLN == ac) + break; + if (ARGS_PUNCT == ac) + break; + + if (NULL == body && ARGS_QWORD != ac && + DELIM_OPEN == mdoc_isdelim(p)) { + if ( ! dword(m, line, la, p, DELIM_OPEN)) + return(0); + continue; + } + + if (NULL == body) { + if ( ! mdoc_body_alloc(m, line, ppos, tok)) + return(0); + body = m->last; + } + + ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p); + + if (MDOC_MAX == ntok) { + if ( ! dword(m, line, la, p, DELIM_MAX)) + return(0); + continue; + } + + if ( ! mdoc_macro(m, ntok, line, la, pos, buf)) + return(0); + break; + } + + /* Clean-ups to leave in a consistent state. */ + + if (NULL == body) { + if ( ! mdoc_body_alloc(m, line, ppos, tok)) + return(0); + body = m->last; + } + + for (n = body->child; n && n->next; n = n->next) + /* Do nothing. */ ; + + /* + * End of sentence spacing: if the last node is a text node and + * has a trailing period, then mark it as being end-of-sentence. + */ + + if (n && MDOC_TEXT == n->type && n->string) + if (mandoc_eos(n->string, strlen(n->string), 1)) + n->flags |= MDOC_EOS; + + /* Up-propagate the end-of-space flag. */ + + if (n && (MDOC_EOS & n->flags)) { + body->flags |= MDOC_EOS; + body->parent->flags |= MDOC_EOS; + } + + /* + * If there is an open sub-block requiring explicit close-out, + * postpone closing out the current block + * until the rew_sub() call closing out the sub-block. + */ + for (n = m->last; n && n != body && n != blk->parent; n = n->parent) { + if (MDOC_BLOCK == n->type && + MDOC_EXPLICIT & mdoc_macros[n->tok].flags && + ! (MDOC_VALID & n->flags)) { + make_pending(n, tok, m, line, ppos); + if ( ! mdoc_endbody_alloc(m, line, ppos, + tok, body, ENDBODY_NOSPACE)) + return(0); + return(1); + } + } + + /* + * If we can't rewind to our body, then our scope has already + * been closed by another macro (like `Oc' closing `Op'). This + * is ugly behaviour nodding its head to OpenBSD's overwhelming + * crufty use of `Op' breakage. + */ + if (n != body) + mandoc_vmsg(MANDOCERR_SCOPENEST, m->parse, line, ppos, + "%s broken", mdoc_macronames[tok]); + + if (n && ! rew_sub(MDOC_BODY, m, tok, line, ppos)) + return(0); + + /* Standard appending of delimiters. */ + + if (nl && ! append_delims(m, line, pos, buf)) + return(0); + + /* Rewind scope, if applicable. */ + + if (n && ! rew_sub(MDOC_BLOCK, m, tok, line, ppos)) + return(0); + + return(1); +} + + +static int +blk_part_exp(MACRO_PROT_ARGS) +{ + int la, nl; + enum margserr ac; + struct mdoc_node *head; /* keep track of head */ + struct mdoc_node *body; /* keep track of body */ + char *p; + enum mdoct ntok; + + nl = MDOC_NEWLINE & m->flags; + + /* + * The opening of an explicit macro having zero or more leading + * punctuation nodes; a head with optional single element (the + * case of `Eo'); and a body that may be empty. + */ + + if ( ! mdoc_block_alloc(m, line, ppos, tok, NULL)) + return(0); + + for (head = body = NULL; ; ) { + la = *pos; + ac = mdoc_args(m, line, pos, buf, tok, &p); + + if (ARGS_ERROR == ac) + return(0); + if (ARGS_PUNCT == ac) + break; + if (ARGS_EOLN == ac) + break; + + /* Flush out leading punctuation. */ + + if (NULL == head && ARGS_QWORD != ac && + DELIM_OPEN == mdoc_isdelim(p)) { + assert(NULL == body); + if ( ! dword(m, line, la, p, DELIM_OPEN)) + return(0); + continue; + } + + if (NULL == head) { + assert(NULL == body); + if ( ! mdoc_head_alloc(m, line, ppos, tok)) + return(0); + head = m->last; + } + + /* + * `Eo' gobbles any data into the head, but most other + * macros just immediately close out and begin the body. + */ + + if (NULL == body) { + assert(head); + /* No check whether it's a macro! */ + if (MDOC_Eo == tok) + if ( ! dword(m, line, la, p, DELIM_MAX)) + return(0); + + if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos)) + return(0); + if ( ! mdoc_body_alloc(m, line, ppos, tok)) + return(0); + body = m->last; + + if (MDOC_Eo == tok) + continue; + } + + assert(NULL != head && NULL != body); + + ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p); + + if (MDOC_MAX == ntok) { + if ( ! dword(m, line, la, p, DELIM_MAX)) + return(0); + continue; + } + + if ( ! mdoc_macro(m, ntok, line, la, pos, buf)) + return(0); + break; + } + + /* Clean-up to leave in a consistent state. */ + + if (NULL == head) + if ( ! mdoc_head_alloc(m, line, ppos, tok)) + return(0); + + if (NULL == body) { + if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos)) + return(0); + if ( ! mdoc_body_alloc(m, line, ppos, tok)) + return(0); + } + + /* Standard appending of delimiters. */ + + if ( ! nl) + return(1); + return(append_delims(m, line, pos, buf)); +} + + +/* ARGSUSED */ +static int +in_line_argn(MACRO_PROT_ARGS) +{ + int la, flushed, j, maxargs, nl; + enum margserr ac; + enum margverr av; + struct mdoc_arg *arg; + char *p; + enum mdoct ntok; + + nl = MDOC_NEWLINE & m->flags; + + /* + * A line macro that has a fixed number of arguments (maxargs). + * Only open the scope once the first non-leading-punctuation is + * found (unless MDOC_IGNDELIM is noted, like in `Pf'), then + * keep it open until the maximum number of arguments are + * exhausted. + */ + + switch (tok) { + case (MDOC_Ap): + /* FALLTHROUGH */ + case (MDOC_No): + /* FALLTHROUGH */ + case (MDOC_Ns): + /* FALLTHROUGH */ + case (MDOC_Ux): + maxargs = 0; + break; + case (MDOC_Bx): + /* FALLTHROUGH */ + case (MDOC_Xr): + maxargs = 2; + break; + default: + maxargs = 1; + break; + } + + for (arg = NULL; ; ) { + la = *pos; + av = mdoc_argv(m, line, tok, &arg, pos, buf); + + if (ARGV_WORD == av) { + *pos = la; + break; + } + + if (ARGV_EOLN == av) + break; + if (ARGV_ARG == av) + continue; + + mdoc_argv_free(arg); + return(0); + } + + for (flushed = j = 0; ; ) { + la = *pos; + ac = mdoc_args(m, line, pos, buf, tok, &p); + + if (ARGS_ERROR == ac) + return(0); + if (ARGS_PUNCT == ac) + break; + if (ARGS_EOLN == ac) + break; + + if ( ! (MDOC_IGNDELIM & mdoc_macros[tok].flags) && + ARGS_QWORD != ac && 0 == j && + DELIM_OPEN == mdoc_isdelim(p)) { + if ( ! dword(m, line, la, p, DELIM_OPEN)) + return(0); + continue; + } else if (0 == j) + if ( ! mdoc_elem_alloc(m, line, la, tok, arg)) + return(0); + + if (j == maxargs && ! flushed) { + if ( ! rew_elem(m, tok)) + return(0); + flushed = 1; + } + + ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p); + + if (MDOC_MAX != ntok) { + if ( ! flushed && ! rew_elem(m, tok)) + return(0); + flushed = 1; + if ( ! mdoc_macro(m, ntok, line, la, pos, buf)) + return(0); + j++; + break; + } + + if ( ! (MDOC_IGNDELIM & mdoc_macros[tok].flags) && + ARGS_QWORD != ac && + ! flushed && + DELIM_NONE != mdoc_isdelim(p)) { + if ( ! rew_elem(m, tok)) + return(0); + flushed = 1; + } + + if ( ! dword(m, line, la, p, DELIM_MAX)) + return(0); + j++; + } + + if (0 == j && ! mdoc_elem_alloc(m, line, la, tok, arg)) + return(0); + + /* Close out in a consistent state. */ + + if ( ! flushed && ! rew_elem(m, tok)) + return(0); + if ( ! nl) + return(1); + return(append_delims(m, line, pos, buf)); +} + + +static int +in_line_eoln(MACRO_PROT_ARGS) +{ + int la; + enum margserr ac; + enum margverr av; + struct mdoc_arg *arg; + char *p; + enum mdoct ntok; + + assert( ! (MDOC_PARSED & mdoc_macros[tok].flags)); + + if (tok == MDOC_Pp) + rew_sub(MDOC_BLOCK, m, MDOC_Nm, line, ppos); + + /* Parse macro arguments. */ + + for (arg = NULL; ; ) { + la = *pos; + av = mdoc_argv(m, line, tok, &arg, pos, buf); + + if (ARGV_WORD == av) { + *pos = la; + break; + } + if (ARGV_EOLN == av) + break; + if (ARGV_ARG == av) + continue; + + mdoc_argv_free(arg); + return(0); + } + + /* Open element scope. */ + + if ( ! mdoc_elem_alloc(m, line, ppos, tok, arg)) + return(0); + + /* Parse argument terms. */ + + for (;;) { + la = *pos; + ac = mdoc_args(m, line, pos, buf, tok, &p); + + if (ARGS_ERROR == ac) + return(0); + if (ARGS_EOLN == ac) + break; + + ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p); + + if (MDOC_MAX == ntok) { + if ( ! dword(m, line, la, p, DELIM_MAX)) + return(0); + continue; + } + + if ( ! rew_elem(m, tok)) + return(0); + return(mdoc_macro(m, ntok, line, la, pos, buf)); + } + + /* Close out (no delimiters). */ + + return(rew_elem(m, tok)); +} + + +/* ARGSUSED */ +static int +ctx_synopsis(MACRO_PROT_ARGS) +{ + int nl; + + nl = MDOC_NEWLINE & m->flags; + + /* If we're not in the SYNOPSIS, go straight to in-line. */ + if ( ! (MDOC_SYNOPSIS & m->flags)) + return(in_line(m, tok, line, ppos, pos, buf)); + + /* If we're a nested call, same place. */ + if ( ! nl) + return(in_line(m, tok, line, ppos, pos, buf)); + + /* + * XXX: this will open a block scope; however, if later we end + * up formatting the block scope, then child nodes will inherit + * the formatting. Be careful. + */ + if (MDOC_Nm == tok) + return(blk_full(m, tok, line, ppos, pos, buf)); + assert(MDOC_Vt == tok); + return(blk_part_imp(m, tok, line, ppos, pos, buf)); +} + + +/* ARGSUSED */ +static int +obsolete(MACRO_PROT_ARGS) +{ + + mdoc_pmsg(m, line, ppos, MANDOCERR_MACROOBS); + return(1); +} + + +/* + * Phrases occur within `Bl -column' entries, separated by `Ta' or tabs. + * They're unusual because they're basically free-form text until a + * macro is encountered. + */ +static int +phrase(struct mdoc *m, int line, int ppos, char *buf) +{ + int la, pos; + enum margserr ac; + enum mdoct ntok; + char *p; + + for (pos = ppos; ; ) { + la = pos; + + ac = mdoc_zargs(m, line, &pos, buf, &p); + + if (ARGS_ERROR == ac) + return(0); + if (ARGS_EOLN == ac) + break; + + ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup_raw(p); + + if (MDOC_MAX == ntok) { + if ( ! dword(m, line, la, p, DELIM_MAX)) + return(0); + continue; + } + + if ( ! mdoc_macro(m, ntok, line, la, &pos, buf)) + return(0); + return(append_delims(m, line, &pos, buf)); + } + + return(1); +} + + +/* ARGSUSED */ +static int +phrase_ta(MACRO_PROT_ARGS) +{ + int la; + enum mdoct ntok; + enum margserr ac; + char *p; + + /* + * FIXME: this is overly restrictive: if the `Ta' is unexpected, + * it should simply error out with ARGSLOST. + */ + + if ( ! rew_sub(MDOC_BODY, m, MDOC_It, line, ppos)) + return(0); + if ( ! mdoc_body_alloc(m, line, ppos, MDOC_It)) + return(0); + + for (;;) { + la = *pos; + ac = mdoc_zargs(m, line, pos, buf, &p); + + if (ARGS_ERROR == ac) + return(0); + if (ARGS_EOLN == ac) + break; + + ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup_raw(p); + + if (MDOC_MAX == ntok) { + if ( ! dword(m, line, la, p, DELIM_MAX)) + return(0); + continue; + } + + if ( ! mdoc_macro(m, ntok, line, la, pos, buf)) + return(0); + return(append_delims(m, line, pos, buf)); + } + + return(1); +} diff --git a/mdoc_man.c b/mdoc_man.c new file mode 100644 index 000000000000..9d7d2ca23861 --- /dev/null +++ b/mdoc_man.c @@ -0,0 +1,637 @@ +/* $Id: mdoc_man.c,v 1.9 2011/10/24 21:47:59 schwarze Exp $ */ +/* + * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include <string.h> + +#include "mandoc.h" +#include "man.h" +#include "mdoc.h" +#include "main.h" + +#define DECL_ARGS const struct mdoc_meta *m, \ + const struct mdoc_node *n, \ + struct mman *mm + +struct mman { + int need_space; /* next word needs prior ws */ + int need_nl; /* next word needs prior nl */ +}; + +struct manact { + int (*cond)(DECL_ARGS); /* DON'T run actions */ + int (*pre)(DECL_ARGS); /* pre-node action */ + void (*post)(DECL_ARGS); /* post-node action */ + const char *prefix; /* pre-node string constant */ + const char *suffix; /* post-node string constant */ +}; + +static int cond_body(DECL_ARGS); +static int cond_head(DECL_ARGS); +static void post_bd(DECL_ARGS); +static void post_dl(DECL_ARGS); +static void post_enc(DECL_ARGS); +static void post_nm(DECL_ARGS); +static void post_percent(DECL_ARGS); +static void post_pf(DECL_ARGS); +static void post_sect(DECL_ARGS); +static void post_sp(DECL_ARGS); +static int pre_ap(DECL_ARGS); +static int pre_bd(DECL_ARGS); +static int pre_br(DECL_ARGS); +static int pre_bx(DECL_ARGS); +static int pre_dl(DECL_ARGS); +static int pre_enc(DECL_ARGS); +static int pre_it(DECL_ARGS); +static int pre_nm(DECL_ARGS); +static int pre_ns(DECL_ARGS); +static int pre_pp(DECL_ARGS); +static int pre_sp(DECL_ARGS); +static int pre_sect(DECL_ARGS); +static int pre_ux(DECL_ARGS); +static int pre_xr(DECL_ARGS); +static void print_word(struct mman *, const char *); +static void print_node(DECL_ARGS); + +static const struct manact manacts[MDOC_MAX + 1] = { + { NULL, pre_ap, NULL, NULL, NULL }, /* Ap */ + { NULL, NULL, NULL, NULL, NULL }, /* Dd */ + { NULL, NULL, NULL, NULL, NULL }, /* Dt */ + { NULL, NULL, NULL, NULL, NULL }, /* Os */ + { NULL, pre_sect, post_sect, ".SH", NULL }, /* Sh */ + { NULL, pre_sect, post_sect, ".SS", NULL }, /* Ss */ + { NULL, pre_pp, NULL, NULL, NULL }, /* Pp */ + { cond_body, pre_dl, post_dl, NULL, NULL }, /* D1 */ + { cond_body, pre_dl, post_dl, NULL, NULL }, /* Dl */ + { cond_body, pre_bd, post_bd, NULL, NULL }, /* Bd */ + { NULL, NULL, NULL, NULL, NULL }, /* Ed */ + { NULL, NULL, NULL, NULL, NULL }, /* Bl */ + { NULL, NULL, NULL, NULL, NULL }, /* El */ + { NULL, pre_it, NULL, NULL, NULL }, /* _It */ + { NULL, pre_enc, post_enc, "\\fI", "\\fP" }, /* Ad */ + { NULL, NULL, NULL, NULL, NULL }, /* _An */ + { NULL, pre_enc, post_enc, "\\fI", "\\fP" }, /* Ar */ + { NULL, pre_enc, post_enc, "\\fB", "\\fP" }, /* Cd */ + { NULL, pre_enc, post_enc, "\\fB", "\\fP" }, /* Cm */ + { NULL, pre_enc, post_enc, "\\fR", "\\fP" }, /* Dv */ + { NULL, pre_enc, post_enc, "\\fR", "\\fP" }, /* Er */ + { NULL, pre_enc, post_enc, "\\fR", "\\fP" }, /* Ev */ + { NULL, pre_enc, post_enc, "The \\fB", + "\\fP\nutility exits 0 on success, and >0 if an error occurs." + }, /* Ex */ + { NULL, NULL, NULL, NULL, NULL }, /* _Fa */ + { NULL, NULL, NULL, NULL, NULL }, /* _Fd */ + { NULL, pre_enc, post_enc, "\\fB-", "\\fP" }, /* Fl */ + { NULL, NULL, NULL, NULL, NULL }, /* _Fn */ + { NULL, NULL, NULL, NULL, NULL }, /* _Ft */ + { NULL, pre_enc, post_enc, "\\fB", "\\fP" }, /* Ic */ + { NULL, NULL, NULL, NULL, NULL }, /* _In */ + { NULL, pre_enc, post_enc, "\\fR", "\\fP" }, /* Li */ + { cond_head, pre_enc, NULL, "\\- ", NULL }, /* Nd */ + { NULL, pre_nm, post_nm, NULL, NULL }, /* Nm */ + { cond_body, pre_enc, post_enc, "[", "]" }, /* Op */ + { NULL, NULL, NULL, NULL, NULL }, /* Ot */ + { NULL, pre_enc, post_enc, "\\fI", "\\fP" }, /* Pa */ + { NULL, pre_enc, post_enc, "The \\fB", + "\\fP\nfunction returns the value 0 if successful;\n" + "otherwise the value -1 is returned and the global\n" + "variable \\fIerrno\\fP is set to indicate the error." + }, /* Rv */ + { NULL, NULL, NULL, NULL, NULL }, /* St */ + { NULL, NULL, NULL, NULL, NULL }, /* _Va */ + { NULL, NULL, NULL, NULL, NULL }, /* _Vt */ + { NULL, pre_xr, NULL, NULL, NULL }, /* Xr */ + { NULL, NULL, post_percent, NULL, NULL }, /* _%A */ + { NULL, NULL, NULL, NULL, NULL }, /* _%B */ + { NULL, NULL, post_percent, NULL, NULL }, /* _%D */ + { NULL, NULL, NULL, NULL, NULL }, /* _%I */ + { NULL, pre_enc, post_percent, "\\fI", "\\fP" }, /* %J */ + { NULL, NULL, NULL, NULL, NULL }, /* _%N */ + { NULL, NULL, NULL, NULL, NULL }, /* _%O */ + { NULL, NULL, NULL, NULL, NULL }, /* _%P */ + { NULL, NULL, NULL, NULL, NULL }, /* _%R */ + { NULL, pre_enc, post_percent, "\"", "\"" }, /* %T */ + { NULL, NULL, NULL, NULL, NULL }, /* _%V */ + { NULL, NULL, NULL, NULL, NULL }, /* Ac */ + { cond_body, pre_enc, post_enc, "<", ">" }, /* Ao */ + { cond_body, pre_enc, post_enc, "<", ">" }, /* Aq */ + { NULL, NULL, NULL, NULL, NULL }, /* At */ + { NULL, NULL, NULL, NULL, NULL }, /* Bc */ + { NULL, NULL, NULL, NULL, NULL }, /* _Bf */ + { cond_body, pre_enc, post_enc, "[", "]" }, /* Bo */ + { cond_body, pre_enc, post_enc, "[", "]" }, /* Bq */ + { NULL, pre_ux, NULL, "BSD/OS", NULL }, /* Bsx */ + { NULL, pre_bx, NULL, NULL, NULL }, /* Bx */ + { NULL, NULL, NULL, NULL, NULL }, /* Db */ + { NULL, NULL, NULL, NULL, NULL }, /* Dc */ + { cond_body, pre_enc, post_enc, "``", "''" }, /* Do */ + { cond_body, pre_enc, post_enc, "``", "''" }, /* Dq */ + { NULL, NULL, NULL, NULL, NULL }, /* _Ec */ + { NULL, NULL, NULL, NULL, NULL }, /* _Ef */ + { NULL, pre_enc, post_enc, "\\fI", "\\fP" }, /* Em */ + { NULL, NULL, NULL, NULL, NULL }, /* _Eo */ + { NULL, pre_ux, NULL, "FreeBSD", NULL }, /* Fx */ + { NULL, pre_enc, post_enc, "\\fB", "\\fP" }, /* Ms */ + { NULL, NULL, NULL, NULL, NULL }, /* No */ + { NULL, pre_ns, NULL, NULL, NULL }, /* Ns */ + { NULL, pre_ux, NULL, "NetBSD", NULL }, /* Nx */ + { NULL, pre_ux, NULL, "OpenBSD", NULL }, /* Ox */ + { NULL, NULL, NULL, NULL, NULL }, /* Pc */ + { NULL, NULL, post_pf, NULL, NULL }, /* Pf */ + { cond_body, pre_enc, post_enc, "(", ")" }, /* Po */ + { cond_body, pre_enc, post_enc, "(", ")" }, /* Pq */ + { NULL, NULL, NULL, NULL, NULL }, /* Qc */ + { cond_body, pre_enc, post_enc, "`", "'" }, /* Ql */ + { cond_body, pre_enc, post_enc, "\"", "\"" }, /* Qo */ + { cond_body, pre_enc, post_enc, "\"", "\"" }, /* Qq */ + { NULL, NULL, NULL, NULL, NULL }, /* Re */ + { cond_body, pre_pp, NULL, NULL, NULL }, /* Rs */ + { NULL, NULL, NULL, NULL, NULL }, /* Sc */ + { cond_body, pre_enc, post_enc, "`", "'" }, /* So */ + { cond_body, pre_enc, post_enc, "`", "'" }, /* Sq */ + { NULL, NULL, NULL, NULL, NULL }, /* _Sm */ + { NULL, pre_enc, post_enc, "\\fI", "\\fP" }, /* Sx */ + { NULL, pre_enc, post_enc, "\\fB", "\\fP" }, /* Sy */ + { NULL, pre_enc, post_enc, "\\fR", "\\fP" }, /* Tn */ + { NULL, pre_ux, NULL, "UNIX", NULL }, /* Ux */ + { NULL, NULL, NULL, NULL, NULL }, /* _Xc */ + { NULL, NULL, NULL, NULL, NULL }, /* _Xo */ + { NULL, NULL, NULL, NULL, NULL }, /* _Fo */ + { NULL, NULL, NULL, NULL, NULL }, /* _Fc */ + { cond_body, pre_enc, post_enc, "[", "]" }, /* Oo */ + { NULL, NULL, NULL, NULL, NULL }, /* Oc */ + { NULL, NULL, NULL, NULL, NULL }, /* _Bk */ + { NULL, NULL, NULL, NULL, NULL }, /* _Ek */ + { NULL, pre_ux, NULL, "is currently in beta test.", NULL }, /* Bt */ + { NULL, NULL, NULL, NULL, NULL }, /* Hf */ + { NULL, NULL, NULL, NULL, NULL }, /* Fr */ + { NULL, pre_ux, NULL, "currently under development.", NULL }, /* Ud */ + { NULL, NULL, NULL, NULL, NULL }, /* _Lb */ + { NULL, pre_pp, NULL, NULL, NULL }, /* Lp */ + { NULL, NULL, NULL, NULL, NULL }, /* _Lk */ + { NULL, NULL, NULL, NULL, NULL }, /* _Mt */ + { cond_body, pre_enc, post_enc, "{", "}" }, /* Brq */ + { cond_body, pre_enc, post_enc, "{", "}" }, /* Bro */ + { NULL, NULL, NULL, NULL, NULL }, /* Brc */ + { NULL, NULL, NULL, NULL, NULL }, /* _%C */ + { NULL, NULL, NULL, NULL, NULL }, /* _Es */ + { NULL, NULL, NULL, NULL, NULL }, /* _En */ + { NULL, pre_ux, NULL, "DragonFly", NULL }, /* Dx */ + { NULL, NULL, NULL, NULL, NULL }, /* _%Q */ + { NULL, pre_br, NULL, NULL, NULL }, /* br */ + { NULL, pre_sp, post_sp, NULL, NULL }, /* sp */ + { NULL, NULL, NULL, NULL, NULL }, /* _%U */ + { NULL, NULL, NULL, NULL, NULL }, /* _Ta */ + { NULL, NULL, NULL, NULL, NULL }, /* ROOT */ +}; + +static void +print_word(struct mman *mm, const char *s) +{ + + if (mm->need_nl) { + /* + * If we need a newline, print it now and start afresh. + */ + putchar('\n'); + mm->need_space = 0; + mm->need_nl = 0; + } else if (mm->need_space && '\0' != s[0]) + /* + * If we need a space, only print it before + * (1) a nonzero length word; + * (2) a word that is non-punctuation; and + * (3) if punctuation, non-terminating puncutation. + */ + if (NULL == strchr(".,:;)]?!", s[0]) || '\0' != s[1]) + putchar(' '); + + /* + * Reassign needing space if we're not following opening + * punctuation. + */ + mm->need_space = + ('(' != s[0] && '[' != s[0]) || '\0' != s[1]; + + for ( ; *s; s++) { + switch (*s) { + case (ASCII_NBRSP): + printf("\\~"); + break; + case (ASCII_HYPH): + putchar('-'); + break; + default: + putchar((unsigned char)*s); + break; + } + } +} + +void +man_man(void *arg, const struct man *man) +{ + + /* + * Dump the keep buffer. + * We're guaranteed by now that this exists (is non-NULL). + * Flush stdout afterward, just in case. + */ + fputs(mparse_getkeep(man_mparse(man)), stdout); + fflush(stdout); +} + +void +man_mdoc(void *arg, const struct mdoc *mdoc) +{ + const struct mdoc_meta *m; + const struct mdoc_node *n; + struct mman mm; + + m = mdoc_meta(mdoc); + n = mdoc_node(mdoc); + + printf(".TH \"%s\" \"%s\" \"%s\" \"%s\" \"%s\"", + m->title, m->msec, m->date, m->os, m->vol); + + memset(&mm, 0, sizeof(struct mman)); + + mm.need_nl = 1; + print_node(m, n, &mm); + putchar('\n'); +} + +static void +print_node(DECL_ARGS) +{ + const struct mdoc_node *prev, *sub; + const struct manact *act; + int cond, do_sub; + + /* + * Break the line if we were parsed subsequent the current node. + * This makes the page structure be more consistent. + */ + prev = n->prev ? n->prev : n->parent; + if (prev && prev->line < n->line) + mm->need_nl = 1; + + act = NULL; + cond = 0; + do_sub = 1; + + if (MDOC_TEXT == n->type) { + /* + * Make sure that we don't happen to start with a + * control character at the start of a line. + */ + if (mm->need_nl && ('.' == *n->string || + '\'' == *n->string)) { + print_word(mm, "\\&"); + mm->need_space = 0; + } + print_word(mm, n->string); + } else { + /* + * Conditionally run the pre-node action handler for a + * node. + */ + act = manacts + n->tok; + cond = NULL == act->cond || (*act->cond)(m, n, mm); + if (cond && act->pre) + do_sub = (*act->pre)(m, n, mm); + } + + /* + * Conditionally run all child nodes. + * Note that this iterates over children instead of using + * recursion. This prevents unnecessary depth in the stack. + */ + if (do_sub) + for (sub = n->child; sub; sub = sub->next) + print_node(m, sub, mm); + + /* + * Lastly, conditionally run the post-node handler. + */ + if (cond && act->post) + (*act->post)(m, n, mm); +} + +static int +cond_head(DECL_ARGS) +{ + + return(MDOC_HEAD == n->type); +} + +static int +cond_body(DECL_ARGS) +{ + + return(MDOC_BODY == n->type); +} + +/* + * Output a font encoding before a node, e.g., \fR. + * This obviously has no trailing space. + */ +static int +pre_enc(DECL_ARGS) +{ + const char *prefix; + + prefix = manacts[n->tok].prefix; + if (NULL == prefix) + return(1); + print_word(mm, prefix); + mm->need_space = 0; + return(1); +} + +/* + * Output a font encoding subsequent a node, e.g., \fP. + */ +static void +post_enc(DECL_ARGS) +{ + const char *suffix; + + suffix = manacts[n->tok].suffix; + if (NULL == suffix) + return; + mm->need_space = 0; + print_word(mm, suffix); +} + +/* + * Used in listings (percent = %A, e.g.). + * FIXME: this is incomplete. + * It doesn't print a nice ", and" for lists. + */ +static void +post_percent(DECL_ARGS) +{ + + post_enc(m, n, mm); + if (n->next) + print_word(mm, ","); + else { + print_word(mm, "."); + mm->need_nl = 1; + } +} + +/* + * Print before a section header. + */ +static int +pre_sect(DECL_ARGS) +{ + + if (MDOC_HEAD != n->type) + return(1); + mm->need_nl = 1; + print_word(mm, manacts[n->tok].prefix); + print_word(mm, "\""); + mm->need_space = 0; + return(1); +} + +/* + * Print subsequent a section header. + */ +static void +post_sect(DECL_ARGS) +{ + + if (MDOC_HEAD != n->type) + return; + mm->need_space = 0; + print_word(mm, "\""); + mm->need_nl = 1; +} + +static int +pre_ap(DECL_ARGS) +{ + + mm->need_space = 0; + print_word(mm, "'"); + mm->need_space = 0; + return(0); +} + +static int +pre_bd(DECL_ARGS) +{ + + if (DISP_unfilled == n->norm->Bd.type || + DISP_literal == n->norm->Bd.type) { + mm->need_nl = 1; + print_word(mm, ".nf"); + } + mm->need_nl = 1; + return(1); +} + +static void +post_bd(DECL_ARGS) +{ + + if (DISP_unfilled == n->norm->Bd.type || + DISP_literal == n->norm->Bd.type) { + mm->need_nl = 1; + print_word(mm, ".fi"); + } + mm->need_nl = 1; +} + +static int +pre_br(DECL_ARGS) +{ + + mm->need_nl = 1; + print_word(mm, ".br"); + mm->need_nl = 1; + return(0); +} + +static int +pre_bx(DECL_ARGS) +{ + + n = n->child; + if (n) { + print_word(mm, n->string); + mm->need_space = 0; + n = n->next; + } + print_word(mm, "BSD"); + if (NULL == n) + return(0); + mm->need_space = 0; + print_word(mm, "-"); + mm->need_space = 0; + print_word(mm, n->string); + return(0); +} + +static int +pre_dl(DECL_ARGS) +{ + + mm->need_nl = 1; + print_word(mm, ".RS 6n"); + mm->need_nl = 1; + return(1); +} + +static void +post_dl(DECL_ARGS) +{ + + mm->need_nl = 1; + print_word(mm, ".RE"); + mm->need_nl = 1; +} + +static int +pre_it(DECL_ARGS) +{ + const struct mdoc_node *bln; + + if (MDOC_HEAD == n->type) { + mm->need_nl = 1; + print_word(mm, ".TP"); + bln = n->parent->parent->prev; + switch (bln->norm->Bl.type) { + case (LIST_bullet): + print_word(mm, "4n"); + mm->need_nl = 1; + print_word(mm, "\\fBo\\fP"); + break; + default: + if (bln->norm->Bl.width) + print_word(mm, bln->norm->Bl.width); + break; + } + mm->need_nl = 1; + } + return(1); +} + +static int +pre_nm(DECL_ARGS) +{ + + if (MDOC_ELEM != n->type && MDOC_HEAD != n->type) + return(1); + print_word(mm, "\\fB"); + mm->need_space = 0; + if (NULL == n->child) + print_word(mm, m->name); + return(1); +} + +static void +post_nm(DECL_ARGS) +{ + + if (MDOC_ELEM != n->type && MDOC_HEAD != n->type) + return; + mm->need_space = 0; + print_word(mm, "\\fP"); +} + +static int +pre_ns(DECL_ARGS) +{ + + mm->need_space = 0; + return(0); +} + +static void +post_pf(DECL_ARGS) +{ + + mm->need_space = 0; +} + +static int +pre_pp(DECL_ARGS) +{ + + mm->need_nl = 1; + if (MDOC_It == n->parent->tok) + print_word(mm, ".sp"); + else + print_word(mm, ".PP"); + mm->need_nl = 1; + return(1); +} + +static int +pre_sp(DECL_ARGS) +{ + + mm->need_nl = 1; + print_word(mm, ".sp"); + return(1); +} + +static void +post_sp(DECL_ARGS) +{ + + mm->need_nl = 1; +} + +static int +pre_xr(DECL_ARGS) +{ + + n = n->child; + if (NULL == n) + return(0); + print_node(m, n, mm); + n = n->next; + if (NULL == n) + return(0); + mm->need_space = 0; + print_word(mm, "("); + print_node(m, n, mm); + print_word(mm, ")"); + return(0); +} + +static int +pre_ux(DECL_ARGS) +{ + + print_word(mm, manacts[n->tok].prefix); + if (NULL == n->child) + return(0); + mm->need_space = 0; + print_word(mm, "\\~"); + mm->need_space = 0; + return(1); +} diff --git a/mdoc_term.c b/mdoc_term.c new file mode 100644 index 000000000000..533356644448 --- /dev/null +++ b/mdoc_term.c @@ -0,0 +1,2257 @@ +/* $Id: mdoc_term.c,v 1.238 2011/11/13 13:15:14 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * Copyright (c) 2010 Ingo Schwarze <schwarze@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/types.h> + +#include <assert.h> +#include <ctype.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "mandoc.h" +#include "out.h" +#include "term.h" +#include "mdoc.h" +#include "main.h" + +struct termpair { + struct termpair *ppair; + int count; +}; + +#define DECL_ARGS struct termp *p, \ + struct termpair *pair, \ + const struct mdoc_meta *m, \ + const struct mdoc_node *n + +struct termact { + int (*pre)(DECL_ARGS); + void (*post)(DECL_ARGS); +}; + +static size_t a2width(const struct termp *, const char *); +static size_t a2height(const struct termp *, const char *); +static size_t a2offs(const struct termp *, const char *); + +static void print_bvspace(struct termp *, + const struct mdoc_node *, + const struct mdoc_node *); +static void print_mdoc_node(DECL_ARGS); +static void print_mdoc_nodelist(DECL_ARGS); +static void print_mdoc_head(struct termp *, const void *); +static void print_mdoc_foot(struct termp *, const void *); +static void synopsis_pre(struct termp *, + const struct mdoc_node *); + +static void termp____post(DECL_ARGS); +static void termp__t_post(DECL_ARGS); +static void termp_an_post(DECL_ARGS); +static void termp_bd_post(DECL_ARGS); +static void termp_bk_post(DECL_ARGS); +static void termp_bl_post(DECL_ARGS); +static void termp_d1_post(DECL_ARGS); +static void termp_fo_post(DECL_ARGS); +static void termp_in_post(DECL_ARGS); +static void termp_it_post(DECL_ARGS); +static void termp_lb_post(DECL_ARGS); +static void termp_nm_post(DECL_ARGS); +static void termp_pf_post(DECL_ARGS); +static void termp_quote_post(DECL_ARGS); +static void termp_sh_post(DECL_ARGS); +static void termp_ss_post(DECL_ARGS); + +static int termp__a_pre(DECL_ARGS); +static int termp__t_pre(DECL_ARGS); +static int termp_an_pre(DECL_ARGS); +static int termp_ap_pre(DECL_ARGS); +static int termp_bd_pre(DECL_ARGS); +static int termp_bf_pre(DECL_ARGS); +static int termp_bk_pre(DECL_ARGS); +static int termp_bl_pre(DECL_ARGS); +static int termp_bold_pre(DECL_ARGS); +static int termp_bt_pre(DECL_ARGS); +static int termp_bx_pre(DECL_ARGS); +static int termp_cd_pre(DECL_ARGS); +static int termp_d1_pre(DECL_ARGS); +static int termp_ex_pre(DECL_ARGS); +static int termp_fa_pre(DECL_ARGS); +static int termp_fd_pre(DECL_ARGS); +static int termp_fl_pre(DECL_ARGS); +static int termp_fn_pre(DECL_ARGS); +static int termp_fo_pre(DECL_ARGS); +static int termp_ft_pre(DECL_ARGS); +static int termp_igndelim_pre(DECL_ARGS); +static int termp_in_pre(DECL_ARGS); +static int termp_it_pre(DECL_ARGS); +static int termp_li_pre(DECL_ARGS); +static int termp_lk_pre(DECL_ARGS); +static int termp_nd_pre(DECL_ARGS); +static int termp_nm_pre(DECL_ARGS); +static int termp_ns_pre(DECL_ARGS); +static int termp_quote_pre(DECL_ARGS); +static int termp_rs_pre(DECL_ARGS); +static int termp_rv_pre(DECL_ARGS); +static int termp_sh_pre(DECL_ARGS); +static int termp_sm_pre(DECL_ARGS); +static int termp_sp_pre(DECL_ARGS); +static int termp_ss_pre(DECL_ARGS); +static int termp_under_pre(DECL_ARGS); +static int termp_ud_pre(DECL_ARGS); +static int termp_vt_pre(DECL_ARGS); +static int termp_xr_pre(DECL_ARGS); +static int termp_xx_pre(DECL_ARGS); + +static const struct termact termacts[MDOC_MAX] = { + { termp_ap_pre, NULL }, /* Ap */ + { NULL, NULL }, /* Dd */ + { NULL, NULL }, /* Dt */ + { NULL, NULL }, /* Os */ + { termp_sh_pre, termp_sh_post }, /* Sh */ + { termp_ss_pre, termp_ss_post }, /* Ss */ + { termp_sp_pre, NULL }, /* Pp */ + { termp_d1_pre, termp_d1_post }, /* D1 */ + { termp_d1_pre, termp_d1_post }, /* Dl */ + { termp_bd_pre, termp_bd_post }, /* Bd */ + { NULL, NULL }, /* Ed */ + { termp_bl_pre, termp_bl_post }, /* Bl */ + { NULL, NULL }, /* El */ + { termp_it_pre, termp_it_post }, /* It */ + { termp_under_pre, NULL }, /* Ad */ + { termp_an_pre, termp_an_post }, /* An */ + { termp_under_pre, NULL }, /* Ar */ + { termp_cd_pre, NULL }, /* Cd */ + { termp_bold_pre, NULL }, /* Cm */ + { NULL, NULL }, /* Dv */ + { NULL, NULL }, /* Er */ + { NULL, NULL }, /* Ev */ + { termp_ex_pre, NULL }, /* Ex */ + { termp_fa_pre, NULL }, /* Fa */ + { termp_fd_pre, NULL }, /* Fd */ + { termp_fl_pre, NULL }, /* Fl */ + { termp_fn_pre, NULL }, /* Fn */ + { termp_ft_pre, NULL }, /* Ft */ + { termp_bold_pre, NULL }, /* Ic */ + { termp_in_pre, termp_in_post }, /* In */ + { termp_li_pre, NULL }, /* Li */ + { termp_nd_pre, NULL }, /* Nd */ + { termp_nm_pre, termp_nm_post }, /* Nm */ + { termp_quote_pre, termp_quote_post }, /* Op */ + { NULL, NULL }, /* Ot */ + { termp_under_pre, NULL }, /* Pa */ + { termp_rv_pre, NULL }, /* Rv */ + { NULL, NULL }, /* St */ + { termp_under_pre, NULL }, /* Va */ + { termp_vt_pre, NULL }, /* Vt */ + { termp_xr_pre, NULL }, /* Xr */ + { termp__a_pre, termp____post }, /* %A */ + { termp_under_pre, termp____post }, /* %B */ + { NULL, termp____post }, /* %D */ + { termp_under_pre, termp____post }, /* %I */ + { termp_under_pre, termp____post }, /* %J */ + { NULL, termp____post }, /* %N */ + { NULL, termp____post }, /* %O */ + { NULL, termp____post }, /* %P */ + { NULL, termp____post }, /* %R */ + { termp__t_pre, termp__t_post }, /* %T */ + { NULL, termp____post }, /* %V */ + { NULL, NULL }, /* Ac */ + { termp_quote_pre, termp_quote_post }, /* Ao */ + { termp_quote_pre, termp_quote_post }, /* Aq */ + { NULL, NULL }, /* At */ + { NULL, NULL }, /* Bc */ + { termp_bf_pre, NULL }, /* Bf */ + { termp_quote_pre, termp_quote_post }, /* Bo */ + { termp_quote_pre, termp_quote_post }, /* Bq */ + { termp_xx_pre, NULL }, /* Bsx */ + { termp_bx_pre, NULL }, /* Bx */ + { NULL, NULL }, /* Db */ + { NULL, NULL }, /* Dc */ + { termp_quote_pre, termp_quote_post }, /* Do */ + { termp_quote_pre, termp_quote_post }, /* Dq */ + { NULL, NULL }, /* Ec */ /* FIXME: no space */ + { NULL, NULL }, /* Ef */ + { termp_under_pre, NULL }, /* Em */ + { termp_quote_pre, termp_quote_post }, /* Eo */ + { termp_xx_pre, NULL }, /* Fx */ + { termp_bold_pre, NULL }, /* Ms */ + { termp_igndelim_pre, NULL }, /* No */ + { termp_ns_pre, NULL }, /* Ns */ + { termp_xx_pre, NULL }, /* Nx */ + { termp_xx_pre, NULL }, /* Ox */ + { NULL, NULL }, /* Pc */ + { termp_igndelim_pre, termp_pf_post }, /* Pf */ + { termp_quote_pre, termp_quote_post }, /* Po */ + { termp_quote_pre, termp_quote_post }, /* Pq */ + { NULL, NULL }, /* Qc */ + { termp_quote_pre, termp_quote_post }, /* Ql */ + { termp_quote_pre, termp_quote_post }, /* Qo */ + { termp_quote_pre, termp_quote_post }, /* Qq */ + { NULL, NULL }, /* Re */ + { termp_rs_pre, NULL }, /* Rs */ + { NULL, NULL }, /* Sc */ + { termp_quote_pre, termp_quote_post }, /* So */ + { termp_quote_pre, termp_quote_post }, /* Sq */ + { termp_sm_pre, NULL }, /* Sm */ + { termp_under_pre, NULL }, /* Sx */ + { termp_bold_pre, NULL }, /* Sy */ + { NULL, NULL }, /* Tn */ + { termp_xx_pre, NULL }, /* Ux */ + { NULL, NULL }, /* Xc */ + { NULL, NULL }, /* Xo */ + { termp_fo_pre, termp_fo_post }, /* Fo */ + { NULL, NULL }, /* Fc */ + { termp_quote_pre, termp_quote_post }, /* Oo */ + { NULL, NULL }, /* Oc */ + { termp_bk_pre, termp_bk_post }, /* Bk */ + { NULL, NULL }, /* Ek */ + { termp_bt_pre, NULL }, /* Bt */ + { NULL, NULL }, /* Hf */ + { NULL, NULL }, /* Fr */ + { termp_ud_pre, NULL }, /* Ud */ + { NULL, termp_lb_post }, /* Lb */ + { termp_sp_pre, NULL }, /* Lp */ + { termp_lk_pre, NULL }, /* Lk */ + { termp_under_pre, NULL }, /* Mt */ + { termp_quote_pre, termp_quote_post }, /* Brq */ + { termp_quote_pre, termp_quote_post }, /* Bro */ + { NULL, NULL }, /* Brc */ + { NULL, termp____post }, /* %C */ + { NULL, NULL }, /* Es */ /* TODO */ + { NULL, NULL }, /* En */ /* TODO */ + { termp_xx_pre, NULL }, /* Dx */ + { NULL, termp____post }, /* %Q */ + { termp_sp_pre, NULL }, /* br */ + { termp_sp_pre, NULL }, /* sp */ + { termp_under_pre, termp____post }, /* %U */ + { NULL, NULL }, /* Ta */ +}; + + +void +terminal_mdoc(void *arg, const struct mdoc *mdoc) +{ + const struct mdoc_node *n; + const struct mdoc_meta *m; + struct termp *p; + + p = (struct termp *)arg; + + if (0 == p->defindent) + p->defindent = 5; + + p->overstep = 0; + p->maxrmargin = p->defrmargin; + p->tabwidth = term_len(p, 5); + + if (NULL == p->symtab) + p->symtab = mchars_alloc(); + + n = mdoc_node(mdoc); + m = mdoc_meta(mdoc); + + term_begin(p, print_mdoc_head, print_mdoc_foot, m); + + if (n->child) + print_mdoc_nodelist(p, NULL, m, n->child); + + term_end(p); +} + + +static void +print_mdoc_nodelist(DECL_ARGS) +{ + + print_mdoc_node(p, pair, m, n); + if (n->next) + print_mdoc_nodelist(p, pair, m, n->next); +} + + +/* ARGSUSED */ +static void +print_mdoc_node(DECL_ARGS) +{ + int chld; + const void *font; + struct termpair npair; + size_t offset, rmargin; + + chld = 1; + offset = p->offset; + rmargin = p->rmargin; + font = term_fontq(p); + + memset(&npair, 0, sizeof(struct termpair)); + npair.ppair = pair; + + /* + * Keeps only work until the end of a line. If a keep was + * invoked in a prior line, revert it to PREKEEP. + * + * Also let SYNPRETTY sections behave as if they were wrapped + * in a `Bk' block. + */ + + if (TERMP_KEEP & p->flags || MDOC_SYNPRETTY & n->flags) { + if (n->prev && n->prev->line != n->line) { + p->flags &= ~TERMP_KEEP; + p->flags |= TERMP_PREKEEP; + } else if (NULL == n->prev) { + if (n->parent && n->parent->line != n->line) { + p->flags &= ~TERMP_KEEP; + p->flags |= TERMP_PREKEEP; + } + } + } + + /* + * Since SYNPRETTY sections aren't "turned off" with `Ek', + * we have to intuit whether we should disable formatting. + */ + + if ( ! (MDOC_SYNPRETTY & n->flags) && + ((n->prev && MDOC_SYNPRETTY & n->prev->flags) || + (n->parent && MDOC_SYNPRETTY & n->parent->flags))) + p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP); + + /* + * After the keep flags have been set up, we may now + * produce output. Note that some pre-handlers do so. + */ + + switch (n->type) { + case (MDOC_TEXT): + if (' ' == *n->string && MDOC_LINE & n->flags) + term_newln(p); + if (MDOC_DELIMC & n->flags) + p->flags |= TERMP_NOSPACE; + term_word(p, n->string); + if (MDOC_DELIMO & n->flags) + p->flags |= TERMP_NOSPACE; + break; + case (MDOC_EQN): + term_eqn(p, n->eqn); + break; + case (MDOC_TBL): + term_tbl(p, n->span); + break; + default: + if (termacts[n->tok].pre && ENDBODY_NOT == n->end) + chld = (*termacts[n->tok].pre) + (p, &npair, m, n); + break; + } + + if (chld && n->child) + print_mdoc_nodelist(p, &npair, m, n->child); + + term_fontpopq(p, font); + + switch (n->type) { + case (MDOC_TEXT): + break; + case (MDOC_TBL): + break; + case (MDOC_EQN): + break; + default: + if ( ! termacts[n->tok].post || MDOC_ENDED & n->flags) + break; + (void)(*termacts[n->tok].post)(p, &npair, m, n); + + /* + * Explicit end tokens not only call the post + * handler, but also tell the respective block + * that it must not call the post handler again. + */ + if (ENDBODY_NOT != n->end) + n->pending->flags |= MDOC_ENDED; + + /* + * End of line terminating an implicit block + * while an explicit block is still open. + * Continue the explicit block without spacing. + */ + if (ENDBODY_NOSPACE == n->end) + p->flags |= TERMP_NOSPACE; + break; + } + + if (MDOC_EOS & n->flags) + p->flags |= TERMP_SENTENCE; + + p->offset = offset; + p->rmargin = rmargin; +} + + +static void +print_mdoc_foot(struct termp *p, const void *arg) +{ + const struct mdoc_meta *m; + + m = (const struct mdoc_meta *)arg; + + term_fontrepl(p, TERMFONT_NONE); + + /* + * Output the footer in new-groff style, that is, three columns + * with the middle being the manual date and flanking columns + * being the operating system: + * + * SYSTEM DATE SYSTEM + */ + + term_vspace(p); + + p->offset = 0; + p->rmargin = (p->maxrmargin - + term_strlen(p, m->date) + term_len(p, 1)) / 2; + p->flags |= TERMP_NOSPACE | TERMP_NOBREAK; + + term_word(p, m->os); + term_flushln(p); + + p->offset = p->rmargin; + p->rmargin = p->maxrmargin - term_strlen(p, m->os); + p->flags |= TERMP_NOSPACE; + + term_word(p, m->date); + term_flushln(p); + + p->offset = p->rmargin; + p->rmargin = p->maxrmargin; + p->flags &= ~TERMP_NOBREAK; + p->flags |= TERMP_NOSPACE; + + term_word(p, m->os); + term_flushln(p); + + p->offset = 0; + p->rmargin = p->maxrmargin; + p->flags = 0; +} + + +static void +print_mdoc_head(struct termp *p, const void *arg) +{ + char buf[BUFSIZ], title[BUFSIZ]; + size_t buflen, titlen; + const struct mdoc_meta *m; + + m = (const struct mdoc_meta *)arg; + + /* + * The header is strange. It has three components, which are + * really two with the first duplicated. It goes like this: + * + * IDENTIFIER TITLE IDENTIFIER + * + * The IDENTIFIER is NAME(SECTION), which is the command-name + * (if given, or "unknown" if not) followed by the manual page + * section. These are given in `Dt'. The TITLE is a free-form + * string depending on the manual volume. If not specified, it + * switches on the manual section. + */ + + p->offset = 0; + p->rmargin = p->maxrmargin; + + assert(m->vol); + strlcpy(buf, m->vol, BUFSIZ); + buflen = term_strlen(p, buf); + + if (m->arch) { + strlcat(buf, " (", BUFSIZ); + strlcat(buf, m->arch, BUFSIZ); + strlcat(buf, ")", BUFSIZ); + } + + snprintf(title, BUFSIZ, "%s(%s)", m->title, m->msec); + titlen = term_strlen(p, title); + + p->flags |= TERMP_NOBREAK | TERMP_NOSPACE; + p->offset = 0; + p->rmargin = 2 * (titlen+1) + buflen < p->maxrmargin ? + (p->maxrmargin - + term_strlen(p, buf) + term_len(p, 1)) / 2 : + p->maxrmargin - buflen; + + term_word(p, title); + term_flushln(p); + + p->flags |= TERMP_NOSPACE; + p->offset = p->rmargin; + p->rmargin = p->offset + buflen + titlen < p->maxrmargin ? + p->maxrmargin - titlen : p->maxrmargin; + + term_word(p, buf); + term_flushln(p); + + p->flags &= ~TERMP_NOBREAK; + if (p->rmargin + titlen <= p->maxrmargin) { + p->flags |= TERMP_NOSPACE; + p->offset = p->rmargin; + p->rmargin = p->maxrmargin; + term_word(p, title); + term_flushln(p); + } + + p->flags &= ~TERMP_NOSPACE; + p->offset = 0; + p->rmargin = p->maxrmargin; +} + + +static size_t +a2height(const struct termp *p, const char *v) +{ + struct roffsu su; + + + assert(v); + if ( ! a2roffsu(v, &su, SCALE_VS)) + SCALE_VS_INIT(&su, atoi(v)); + + return(term_vspan(p, &su)); +} + + +static size_t +a2width(const struct termp *p, const char *v) +{ + struct roffsu su; + + assert(v); + if ( ! a2roffsu(v, &su, SCALE_MAX)) + SCALE_HS_INIT(&su, term_strlen(p, v)); + + return(term_hspan(p, &su)); +} + + +static size_t +a2offs(const struct termp *p, const char *v) +{ + struct roffsu su; + + if ('\0' == *v) + return(0); + else if (0 == strcmp(v, "left")) + return(0); + else if (0 == strcmp(v, "indent")) + return(term_len(p, p->defindent + 1)); + else if (0 == strcmp(v, "indent-two")) + return(term_len(p, (p->defindent + 1) * 2)); + else if ( ! a2roffsu(v, &su, SCALE_MAX)) + SCALE_HS_INIT(&su, term_strlen(p, v)); + + return(term_hspan(p, &su)); +} + + +/* + * Determine how much space to print out before block elements of `It' + * (and thus `Bl') and `Bd'. And then go ahead and print that space, + * too. + */ +static void +print_bvspace(struct termp *p, + const struct mdoc_node *bl, + const struct mdoc_node *n) +{ + const struct mdoc_node *nn; + + assert(n); + + term_newln(p); + + if (MDOC_Bd == bl->tok && bl->norm->Bd.comp) + return; + if (MDOC_Bl == bl->tok && bl->norm->Bl.comp) + return; + + /* Do not vspace directly after Ss/Sh. */ + + for (nn = n; nn; nn = nn->parent) { + if (MDOC_BLOCK != nn->type) + continue; + if (MDOC_Ss == nn->tok) + return; + if (MDOC_Sh == nn->tok) + return; + if (NULL == nn->prev) + continue; + break; + } + + /* A `-column' does not assert vspace within the list. */ + + if (MDOC_Bl == bl->tok && LIST_column == bl->norm->Bl.type) + if (n->prev && MDOC_It == n->prev->tok) + return; + + /* A `-diag' without body does not vspace. */ + + if (MDOC_Bl == bl->tok && LIST_diag == bl->norm->Bl.type) + if (n->prev && MDOC_It == n->prev->tok) { + assert(n->prev->body); + if (NULL == n->prev->body->child) + return; + } + + term_vspace(p); +} + + +/* ARGSUSED */ +static int +termp_it_pre(DECL_ARGS) +{ + const struct mdoc_node *bl, *nn; + char buf[7]; + int i; + size_t width, offset, ncols, dcol; + enum mdoc_list type; + + if (MDOC_BLOCK == n->type) { + print_bvspace(p, n->parent->parent, n); + return(1); + } + + bl = n->parent->parent->parent; + type = bl->norm->Bl.type; + + /* + * First calculate width and offset. This is pretty easy unless + * we're a -column list, in which case all prior columns must + * be accounted for. + */ + + width = offset = 0; + + if (bl->norm->Bl.offs) + offset = a2offs(p, bl->norm->Bl.offs); + + switch (type) { + case (LIST_column): + if (MDOC_HEAD == n->type) + break; + + /* + * Imitate groff's column handling: + * - For each earlier column, add its width. + * - For less than 5 columns, add four more blanks per + * column. + * - For exactly 5 columns, add three more blank per + * column. + * - For more than 5 columns, add only one column. + */ + ncols = bl->norm->Bl.ncols; + + /* LINTED */ + dcol = ncols < 5 ? term_len(p, 4) : + ncols == 5 ? term_len(p, 3) : term_len(p, 1); + + /* + * Calculate the offset by applying all prior MDOC_BODY, + * so we stop at the MDOC_HEAD (NULL == nn->prev). + */ + + for (i = 0, nn = n->prev; + nn->prev && i < (int)ncols; + nn = nn->prev, i++) + offset += dcol + a2width + (p, bl->norm->Bl.cols[i]); + + /* + * When exceeding the declared number of columns, leave + * the remaining widths at 0. This will later be + * adjusted to the default width of 10, or, for the last + * column, stretched to the right margin. + */ + if (i >= (int)ncols) + break; + + /* + * Use the declared column widths, extended as explained + * in the preceding paragraph. + */ + width = a2width(p, bl->norm->Bl.cols[i]) + dcol; + break; + default: + if (NULL == bl->norm->Bl.width) + break; + + /* + * Note: buffer the width by 2, which is groff's magic + * number for buffering single arguments. See the above + * handling for column for how this changes. + */ + assert(bl->norm->Bl.width); + width = a2width(p, bl->norm->Bl.width) + term_len(p, 2); + break; + } + + /* + * List-type can override the width in the case of fixed-head + * values (bullet, dash/hyphen, enum). Tags need a non-zero + * offset. + */ + + switch (type) { + case (LIST_bullet): + /* FALLTHROUGH */ + case (LIST_dash): + /* FALLTHROUGH */ + case (LIST_hyphen): + if (width < term_len(p, 4)) + width = term_len(p, 4); + break; + case (LIST_enum): + if (width < term_len(p, 5)) + width = term_len(p, 5); + break; + case (LIST_hang): + if (0 == width) + width = term_len(p, 8); + break; + case (LIST_column): + /* FALLTHROUGH */ + case (LIST_tag): + if (0 == width) + width = term_len(p, 10); + break; + default: + break; + } + + /* + * Whitespace control. Inset bodies need an initial space, + * while diagonal bodies need two. + */ + + p->flags |= TERMP_NOSPACE; + + switch (type) { + case (LIST_diag): + if (MDOC_BODY == n->type) + term_word(p, "\\ \\ "); + break; + case (LIST_inset): + if (MDOC_BODY == n->type) + term_word(p, "\\ "); + break; + default: + break; + } + + p->flags |= TERMP_NOSPACE; + + switch (type) { + case (LIST_diag): + if (MDOC_HEAD == n->type) + term_fontpush(p, TERMFONT_BOLD); + break; + default: + break; + } + + /* + * Pad and break control. This is the tricky part. These flags + * are documented in term_flushln() in term.c. Note that we're + * going to unset all of these flags in termp_it_post() when we + * exit. + */ + + switch (type) { + case (LIST_bullet): + /* FALLTHROUGH */ + case (LIST_dash): + /* FALLTHROUGH */ + case (LIST_enum): + /* FALLTHROUGH */ + case (LIST_hyphen): + if (MDOC_HEAD == n->type) + p->flags |= TERMP_NOBREAK; + break; + case (LIST_hang): + if (MDOC_HEAD == n->type) + p->flags |= TERMP_NOBREAK; + else + break; + + /* + * This is ugly. If `-hang' is specified and the body + * is a `Bl' or `Bd', then we want basically to nullify + * the "overstep" effect in term_flushln() and treat + * this as a `-ohang' list instead. + */ + if (n->next->child && + (MDOC_Bl == n->next->child->tok || + MDOC_Bd == n->next->child->tok)) + p->flags &= ~TERMP_NOBREAK; + else + p->flags |= TERMP_HANG; + break; + case (LIST_tag): + if (MDOC_HEAD == n->type) + p->flags |= TERMP_NOBREAK | TERMP_TWOSPACE; + + if (MDOC_HEAD != n->type) + break; + if (NULL == n->next || NULL == n->next->child) + p->flags |= TERMP_DANGLE; + break; + case (LIST_column): + if (MDOC_HEAD == n->type) + break; + + if (NULL == n->next) + p->flags &= ~TERMP_NOBREAK; + else + p->flags |= TERMP_NOBREAK; + + break; + case (LIST_diag): + if (MDOC_HEAD == n->type) + p->flags |= TERMP_NOBREAK; + break; + default: + break; + } + + /* + * Margin control. Set-head-width lists have their right + * margins shortened. The body for these lists has the offset + * necessarily lengthened. Everybody gets the offset. + */ + + p->offset += offset; + + switch (type) { + case (LIST_hang): + /* + * Same stipulation as above, regarding `-hang'. We + * don't want to recalculate rmargin and offsets when + * using `Bd' or `Bl' within `-hang' overstep lists. + */ + if (MDOC_HEAD == n->type && n->next->child && + (MDOC_Bl == n->next->child->tok || + MDOC_Bd == n->next->child->tok)) + break; + /* FALLTHROUGH */ + case (LIST_bullet): + /* FALLTHROUGH */ + case (LIST_dash): + /* FALLTHROUGH */ + case (LIST_enum): + /* FALLTHROUGH */ + case (LIST_hyphen): + /* FALLTHROUGH */ + case (LIST_tag): + assert(width); + if (MDOC_HEAD == n->type) + p->rmargin = p->offset + width; + else + p->offset += width; + break; + case (LIST_column): + assert(width); + p->rmargin = p->offset + width; + /* + * XXX - this behaviour is not documented: the + * right-most column is filled to the right margin. + */ + if (MDOC_HEAD == n->type) + break; + if (NULL == n->next && p->rmargin < p->maxrmargin) + p->rmargin = p->maxrmargin; + break; + default: + break; + } + + /* + * The dash, hyphen, bullet and enum lists all have a special + * HEAD character (temporarily bold, in some cases). + */ + + if (MDOC_HEAD == n->type) + switch (type) { + case (LIST_bullet): + term_fontpush(p, TERMFONT_BOLD); + term_word(p, "\\[bu]"); + term_fontpop(p); + break; + case (LIST_dash): + /* FALLTHROUGH */ + case (LIST_hyphen): + term_fontpush(p, TERMFONT_BOLD); + term_word(p, "\\(hy"); + term_fontpop(p); + break; + case (LIST_enum): + (pair->ppair->ppair->count)++; + snprintf(buf, sizeof(buf), "%d.", + pair->ppair->ppair->count); + term_word(p, buf); + break; + default: + break; + } + + /* + * If we're not going to process our children, indicate so here. + */ + + switch (type) { + case (LIST_bullet): + /* FALLTHROUGH */ + case (LIST_item): + /* FALLTHROUGH */ + case (LIST_dash): + /* FALLTHROUGH */ + case (LIST_hyphen): + /* FALLTHROUGH */ + case (LIST_enum): + if (MDOC_HEAD == n->type) + return(0); + break; + case (LIST_column): + if (MDOC_HEAD == n->type) + return(0); + break; + default: + break; + } + + return(1); +} + + +/* ARGSUSED */ +static void +termp_it_post(DECL_ARGS) +{ + enum mdoc_list type; + + if (MDOC_BLOCK == n->type) + return; + + type = n->parent->parent->parent->norm->Bl.type; + + switch (type) { + case (LIST_item): + /* FALLTHROUGH */ + case (LIST_diag): + /* FALLTHROUGH */ + case (LIST_inset): + if (MDOC_BODY == n->type) + term_newln(p); + break; + case (LIST_column): + if (MDOC_BODY == n->type) + term_flushln(p); + break; + default: + term_newln(p); + break; + } + + /* + * Now that our output is flushed, we can reset our tags. Since + * only `It' sets these flags, we're free to assume that nobody + * has munged them in the meanwhile. + */ + + p->flags &= ~TERMP_DANGLE; + p->flags &= ~TERMP_NOBREAK; + p->flags &= ~TERMP_TWOSPACE; + p->flags &= ~TERMP_HANG; +} + + +/* ARGSUSED */ +static int +termp_nm_pre(DECL_ARGS) +{ + + if (MDOC_BLOCK == n->type) + return(1); + + if (MDOC_BODY == n->type) { + if (NULL == n->child) + return(0); + p->flags |= TERMP_NOSPACE; + p->offset += term_len(p, 1) + + (NULL == n->prev->child ? term_strlen(p, m->name) : + MDOC_TEXT == n->prev->child->type ? + term_strlen(p, n->prev->child->string) : + term_len(p, 5)); + return(1); + } + + if (NULL == n->child && NULL == m->name) + return(0); + + if (MDOC_HEAD == n->type) + synopsis_pre(p, n->parent); + + if (MDOC_HEAD == n->type && n->next->child) { + p->flags |= TERMP_NOSPACE | TERMP_NOBREAK; + p->rmargin = p->offset + term_len(p, 1); + if (NULL == n->child) { + p->rmargin += term_strlen(p, m->name); + } else if (MDOC_TEXT == n->child->type) { + p->rmargin += term_strlen(p, n->child->string); + if (n->child->next) + p->flags |= TERMP_HANG; + } else { + p->rmargin += term_len(p, 5); + p->flags |= TERMP_HANG; + } + } + + term_fontpush(p, TERMFONT_BOLD); + if (NULL == n->child) + term_word(p, m->name); + return(1); +} + + +/* ARGSUSED */ +static void +termp_nm_post(DECL_ARGS) +{ + + if (MDOC_HEAD == n->type && n->next->child) { + term_flushln(p); + p->flags &= ~(TERMP_NOBREAK | TERMP_HANG); + } else if (MDOC_BODY == n->type && n->child) + term_flushln(p); +} + + +/* ARGSUSED */ +static int +termp_fl_pre(DECL_ARGS) +{ + + term_fontpush(p, TERMFONT_BOLD); + term_word(p, "\\-"); + + if (n->child) + p->flags |= TERMP_NOSPACE; + else if (n->next && n->next->line == n->line) + p->flags |= TERMP_NOSPACE; + + return(1); +} + + +/* ARGSUSED */ +static int +termp__a_pre(DECL_ARGS) +{ + + if (n->prev && MDOC__A == n->prev->tok) + if (NULL == n->next || MDOC__A != n->next->tok) + term_word(p, "and"); + + return(1); +} + + +/* ARGSUSED */ +static int +termp_an_pre(DECL_ARGS) +{ + + if (NULL == n->child) + return(1); + + /* + * If not in the AUTHORS section, `An -split' will cause + * newlines to occur before the author name. If in the AUTHORS + * section, by default, the first `An' invocation is nosplit, + * then all subsequent ones, regardless of whether interspersed + * with other macros/text, are split. -split, in this case, + * will override the condition of the implied first -nosplit. + */ + + if (n->sec == SEC_AUTHORS) { + if ( ! (TERMP_ANPREC & p->flags)) { + if (TERMP_SPLIT & p->flags) + term_newln(p); + return(1); + } + if (TERMP_NOSPLIT & p->flags) + return(1); + term_newln(p); + return(1); + } + + if (TERMP_SPLIT & p->flags) + term_newln(p); + + return(1); +} + + +/* ARGSUSED */ +static void +termp_an_post(DECL_ARGS) +{ + + if (n->child) { + if (SEC_AUTHORS == n->sec) + p->flags |= TERMP_ANPREC; + return; + } + + if (AUTH_split == n->norm->An.auth) { + p->flags &= ~TERMP_NOSPLIT; + p->flags |= TERMP_SPLIT; + } else if (AUTH_nosplit == n->norm->An.auth) { + p->flags &= ~TERMP_SPLIT; + p->flags |= TERMP_NOSPLIT; + } + +} + + +/* ARGSUSED */ +static int +termp_ns_pre(DECL_ARGS) +{ + + if ( ! (MDOC_LINE & n->flags)) + p->flags |= TERMP_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static int +termp_rs_pre(DECL_ARGS) +{ + + if (SEC_SEE_ALSO != n->sec) + return(1); + if (MDOC_BLOCK == n->type && n->prev) + term_vspace(p); + return(1); +} + + +/* ARGSUSED */ +static int +termp_rv_pre(DECL_ARGS) +{ + int nchild; + + term_newln(p); + term_word(p, "The"); + + nchild = n->nchild; + for (n = n->child; n; n = n->next) { + term_fontpush(p, TERMFONT_BOLD); + term_word(p, n->string); + term_fontpop(p); + + p->flags |= TERMP_NOSPACE; + term_word(p, "()"); + + if (nchild > 2 && n->next) { + p->flags |= TERMP_NOSPACE; + term_word(p, ","); + } + + if (n->next && NULL == n->next->next) + term_word(p, "and"); + } + + if (nchild > 1) + term_word(p, "functions return"); + else + term_word(p, "function returns"); + + term_word(p, "the value 0 if successful; otherwise the value " + "-1 is returned and the global variable"); + + term_fontpush(p, TERMFONT_UNDER); + term_word(p, "errno"); + term_fontpop(p); + + term_word(p, "is set to indicate the error."); + p->flags |= TERMP_SENTENCE; + + return(0); +} + + +/* ARGSUSED */ +static int +termp_ex_pre(DECL_ARGS) +{ + int nchild; + + term_newln(p); + term_word(p, "The"); + + nchild = n->nchild; + for (n = n->child; n; n = n->next) { + term_fontpush(p, TERMFONT_BOLD); + term_word(p, n->string); + term_fontpop(p); + + if (nchild > 2 && n->next) { + p->flags |= TERMP_NOSPACE; + term_word(p, ","); + } + + if (n->next && NULL == n->next->next) + term_word(p, "and"); + } + + if (nchild > 1) + term_word(p, "utilities exit"); + else + term_word(p, "utility exits"); + + term_word(p, "0 on success, and >0 if an error occurs."); + + p->flags |= TERMP_SENTENCE; + return(0); +} + + +/* ARGSUSED */ +static int +termp_nd_pre(DECL_ARGS) +{ + + if (MDOC_BODY != n->type) + return(1); + +#if defined(__OpenBSD__) || defined(__linux__) + term_word(p, "\\(en"); +#else + term_word(p, "\\(em"); +#endif + return(1); +} + + +/* ARGSUSED */ +static int +termp_bl_pre(DECL_ARGS) +{ + + return(MDOC_HEAD != n->type); +} + + +/* ARGSUSED */ +static void +termp_bl_post(DECL_ARGS) +{ + + if (MDOC_BLOCK == n->type) + term_newln(p); +} + +/* ARGSUSED */ +static int +termp_xr_pre(DECL_ARGS) +{ + + if (NULL == (n = n->child)) + return(0); + + assert(MDOC_TEXT == n->type); + term_word(p, n->string); + + if (NULL == (n = n->next)) + return(0); + + p->flags |= TERMP_NOSPACE; + term_word(p, "("); + p->flags |= TERMP_NOSPACE; + + assert(MDOC_TEXT == n->type); + term_word(p, n->string); + + p->flags |= TERMP_NOSPACE; + term_word(p, ")"); + + return(0); +} + +/* + * This decides how to assert whitespace before any of the SYNOPSIS set + * of macros (which, as in the case of Ft/Fo and Ft/Fn, may contain + * macro combos). + */ +static void +synopsis_pre(struct termp *p, const struct mdoc_node *n) +{ + /* + * Obviously, if we're not in a SYNOPSIS or no prior macros + * exist, do nothing. + */ + if (NULL == n->prev || ! (MDOC_SYNPRETTY & n->flags)) + return; + + /* + * If we're the second in a pair of like elements, emit our + * newline and return. UNLESS we're `Fo', `Fn', `Fn', in which + * case we soldier on. + */ + if (n->prev->tok == n->tok && + MDOC_Ft != n->tok && + MDOC_Fo != n->tok && + MDOC_Fn != n->tok) { + term_newln(p); + return; + } + + /* + * If we're one of the SYNOPSIS set and non-like pair-wise after + * another (or Fn/Fo, which we've let slip through) then assert + * vertical space, else only newline and move on. + */ + switch (n->prev->tok) { + case (MDOC_Fd): + /* FALLTHROUGH */ + case (MDOC_Fn): + /* FALLTHROUGH */ + case (MDOC_Fo): + /* FALLTHROUGH */ + case (MDOC_In): + /* FALLTHROUGH */ + case (MDOC_Vt): + term_vspace(p); + break; + case (MDOC_Ft): + if (MDOC_Fn != n->tok && MDOC_Fo != n->tok) { + term_vspace(p); + break; + } + /* FALLTHROUGH */ + default: + term_newln(p); + break; + } +} + + +static int +termp_vt_pre(DECL_ARGS) +{ + + if (MDOC_ELEM == n->type) { + synopsis_pre(p, n); + return(termp_under_pre(p, pair, m, n)); + } else if (MDOC_BLOCK == n->type) { + synopsis_pre(p, n); + return(1); + } else if (MDOC_HEAD == n->type) + return(0); + + return(termp_under_pre(p, pair, m, n)); +} + + +/* ARGSUSED */ +static int +termp_bold_pre(DECL_ARGS) +{ + + term_fontpush(p, TERMFONT_BOLD); + return(1); +} + + +/* ARGSUSED */ +static int +termp_fd_pre(DECL_ARGS) +{ + + synopsis_pre(p, n); + return(termp_bold_pre(p, pair, m, n)); +} + + +/* ARGSUSED */ +static int +termp_sh_pre(DECL_ARGS) +{ + + /* No vspace between consecutive `Sh' calls. */ + + switch (n->type) { + case (MDOC_BLOCK): + if (n->prev && MDOC_Sh == n->prev->tok) + if (NULL == n->prev->body->child) + break; + term_vspace(p); + break; + case (MDOC_HEAD): + term_fontpush(p, TERMFONT_BOLD); + break; + case (MDOC_BODY): + p->offset = term_len(p, p->defindent); + break; + default: + break; + } + return(1); +} + + +/* ARGSUSED */ +static void +termp_sh_post(DECL_ARGS) +{ + + switch (n->type) { + case (MDOC_HEAD): + term_newln(p); + break; + case (MDOC_BODY): + term_newln(p); + p->offset = 0; + break; + default: + break; + } +} + + +/* ARGSUSED */ +static int +termp_bt_pre(DECL_ARGS) +{ + + term_word(p, "is currently in beta test."); + p->flags |= TERMP_SENTENCE; + return(0); +} + + +/* ARGSUSED */ +static void +termp_lb_post(DECL_ARGS) +{ + + if (SEC_LIBRARY == n->sec && MDOC_LINE & n->flags) + term_newln(p); +} + + +/* ARGSUSED */ +static int +termp_ud_pre(DECL_ARGS) +{ + + term_word(p, "currently under development."); + p->flags |= TERMP_SENTENCE; + return(0); +} + + +/* ARGSUSED */ +static int +termp_d1_pre(DECL_ARGS) +{ + + if (MDOC_BLOCK != n->type) + return(1); + term_newln(p); + p->offset += term_len(p, p->defindent + 1); + return(1); +} + + +/* ARGSUSED */ +static void +termp_d1_post(DECL_ARGS) +{ + + if (MDOC_BLOCK != n->type) + return; + term_newln(p); +} + + +/* ARGSUSED */ +static int +termp_ft_pre(DECL_ARGS) +{ + + /* NB: MDOC_LINE does not effect this! */ + synopsis_pre(p, n); + term_fontpush(p, TERMFONT_UNDER); + return(1); +} + + +/* ARGSUSED */ +static int +termp_fn_pre(DECL_ARGS) +{ + int pretty; + + pretty = MDOC_SYNPRETTY & n->flags; + + synopsis_pre(p, n); + + if (NULL == (n = n->child)) + return(0); + + assert(MDOC_TEXT == n->type); + term_fontpush(p, TERMFONT_BOLD); + term_word(p, n->string); + term_fontpop(p); + + p->flags |= TERMP_NOSPACE; + term_word(p, "("); + p->flags |= TERMP_NOSPACE; + + for (n = n->next; n; n = n->next) { + assert(MDOC_TEXT == n->type); + term_fontpush(p, TERMFONT_UNDER); + term_word(p, n->string); + term_fontpop(p); + + if (n->next) { + p->flags |= TERMP_NOSPACE; + term_word(p, ","); + } + } + + p->flags |= TERMP_NOSPACE; + term_word(p, ")"); + + if (pretty) { + p->flags |= TERMP_NOSPACE; + term_word(p, ";"); + } + + return(0); +} + + +/* ARGSUSED */ +static int +termp_fa_pre(DECL_ARGS) +{ + const struct mdoc_node *nn; + + if (n->parent->tok != MDOC_Fo) { + term_fontpush(p, TERMFONT_UNDER); + return(1); + } + + for (nn = n->child; nn; nn = nn->next) { + term_fontpush(p, TERMFONT_UNDER); + term_word(p, nn->string); + term_fontpop(p); + + if (nn->next) { + p->flags |= TERMP_NOSPACE; + term_word(p, ","); + } + } + + if (n->child && n->next && n->next->tok == MDOC_Fa) { + p->flags |= TERMP_NOSPACE; + term_word(p, ","); + } + + return(0); +} + + +/* ARGSUSED */ +static int +termp_bd_pre(DECL_ARGS) +{ + size_t tabwidth, rm, rmax; + const struct mdoc_node *nn; + + if (MDOC_BLOCK == n->type) { + print_bvspace(p, n, n); + return(1); + } else if (MDOC_HEAD == n->type) + return(0); + + if (n->norm->Bd.offs) + p->offset += a2offs(p, n->norm->Bd.offs); + + /* + * If -ragged or -filled are specified, the block does nothing + * but change the indentation. If -unfilled or -literal are + * specified, text is printed exactly as entered in the display: + * for macro lines, a newline is appended to the line. Blank + * lines are allowed. + */ + + if (DISP_literal != n->norm->Bd.type && + DISP_unfilled != n->norm->Bd.type) + return(1); + + tabwidth = p->tabwidth; + if (DISP_literal == n->norm->Bd.type) + p->tabwidth = term_len(p, 8); + + rm = p->rmargin; + rmax = p->maxrmargin; + p->rmargin = p->maxrmargin = TERM_MAXMARGIN; + + for (nn = n->child; nn; nn = nn->next) { + print_mdoc_node(p, pair, m, nn); + /* + * If the printed node flushes its own line, then we + * needn't do it here as well. This is hacky, but the + * notion of selective eoln whitespace is pretty dumb + * anyway, so don't sweat it. + */ + switch (nn->tok) { + case (MDOC_Sm): + /* FALLTHROUGH */ + case (MDOC_br): + /* FALLTHROUGH */ + case (MDOC_sp): + /* FALLTHROUGH */ + case (MDOC_Bl): + /* FALLTHROUGH */ + case (MDOC_D1): + /* FALLTHROUGH */ + case (MDOC_Dl): + /* FALLTHROUGH */ + case (MDOC_Lp): + /* FALLTHROUGH */ + case (MDOC_Pp): + continue; + default: + break; + } + if (nn->next && nn->next->line == nn->line) + continue; + term_flushln(p); + p->flags |= TERMP_NOSPACE; + } + + p->tabwidth = tabwidth; + p->rmargin = rm; + p->maxrmargin = rmax; + return(0); +} + + +/* ARGSUSED */ +static void +termp_bd_post(DECL_ARGS) +{ + size_t rm, rmax; + + if (MDOC_BODY != n->type) + return; + + rm = p->rmargin; + rmax = p->maxrmargin; + + if (DISP_literal == n->norm->Bd.type || + DISP_unfilled == n->norm->Bd.type) + p->rmargin = p->maxrmargin = TERM_MAXMARGIN; + + p->flags |= TERMP_NOSPACE; + term_newln(p); + + p->rmargin = rm; + p->maxrmargin = rmax; +} + + +/* ARGSUSED */ +static int +termp_bx_pre(DECL_ARGS) +{ + + if (NULL != (n = n->child)) { + term_word(p, n->string); + p->flags |= TERMP_NOSPACE; + term_word(p, "BSD"); + } else { + term_word(p, "BSD"); + return(0); + } + + if (NULL != (n = n->next)) { + p->flags |= TERMP_NOSPACE; + term_word(p, "-"); + p->flags |= TERMP_NOSPACE; + term_word(p, n->string); + } + + return(0); +} + + +/* ARGSUSED */ +static int +termp_xx_pre(DECL_ARGS) +{ + const char *pp; + int flags; + + pp = NULL; + switch (n->tok) { + case (MDOC_Bsx): + pp = "BSD/OS"; + break; + case (MDOC_Dx): + pp = "DragonFly"; + break; + case (MDOC_Fx): + pp = "FreeBSD"; + break; + case (MDOC_Nx): + pp = "NetBSD"; + break; + case (MDOC_Ox): + pp = "OpenBSD"; + break; + case (MDOC_Ux): + pp = "UNIX"; + break; + default: + break; + } + + term_word(p, pp); + if (n->child) { + flags = p->flags; + p->flags |= TERMP_KEEP; + term_word(p, n->child->string); + p->flags = flags; + } + return(0); +} + + +/* ARGSUSED */ +static int +termp_igndelim_pre(DECL_ARGS) +{ + + p->flags |= TERMP_IGNDELIM; + return(1); +} + + +/* ARGSUSED */ +static void +termp_pf_post(DECL_ARGS) +{ + + p->flags |= TERMP_NOSPACE; +} + + +/* ARGSUSED */ +static int +termp_ss_pre(DECL_ARGS) +{ + + switch (n->type) { + case (MDOC_BLOCK): + term_newln(p); + if (n->prev) + term_vspace(p); + break; + case (MDOC_HEAD): + term_fontpush(p, TERMFONT_BOLD); + p->offset = term_len(p, (p->defindent+1)/2); + break; + default: + break; + } + + return(1); +} + + +/* ARGSUSED */ +static void +termp_ss_post(DECL_ARGS) +{ + + if (MDOC_HEAD == n->type) + term_newln(p); +} + + +/* ARGSUSED */ +static int +termp_cd_pre(DECL_ARGS) +{ + + synopsis_pre(p, n); + term_fontpush(p, TERMFONT_BOLD); + return(1); +} + + +/* ARGSUSED */ +static int +termp_in_pre(DECL_ARGS) +{ + + synopsis_pre(p, n); + + if (MDOC_SYNPRETTY & n->flags && MDOC_LINE & n->flags) { + term_fontpush(p, TERMFONT_BOLD); + term_word(p, "#include"); + term_word(p, "<"); + } else { + term_word(p, "<"); + term_fontpush(p, TERMFONT_UNDER); + } + + p->flags |= TERMP_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static void +termp_in_post(DECL_ARGS) +{ + + if (MDOC_SYNPRETTY & n->flags) + term_fontpush(p, TERMFONT_BOLD); + + p->flags |= TERMP_NOSPACE; + term_word(p, ">"); + + if (MDOC_SYNPRETTY & n->flags) + term_fontpop(p); +} + + +/* ARGSUSED */ +static int +termp_sp_pre(DECL_ARGS) +{ + size_t i, len; + + switch (n->tok) { + case (MDOC_sp): + len = n->child ? a2height(p, n->child->string) : 1; + break; + case (MDOC_br): + len = 0; + break; + default: + len = 1; + break; + } + + if (0 == len) + term_newln(p); + for (i = 0; i < len; i++) + term_vspace(p); + + return(0); +} + + +/* ARGSUSED */ +static int +termp_quote_pre(DECL_ARGS) +{ + + if (MDOC_BODY != n->type && MDOC_ELEM != n->type) + return(1); + + switch (n->tok) { + case (MDOC_Ao): + /* FALLTHROUGH */ + case (MDOC_Aq): + term_word(p, "<"); + break; + case (MDOC_Bro): + /* FALLTHROUGH */ + case (MDOC_Brq): + term_word(p, "{"); + break; + case (MDOC_Oo): + /* FALLTHROUGH */ + case (MDOC_Op): + /* FALLTHROUGH */ + case (MDOC_Bo): + /* FALLTHROUGH */ + case (MDOC_Bq): + term_word(p, "["); + break; + case (MDOC_Do): + /* FALLTHROUGH */ + case (MDOC_Dq): + term_word(p, "``"); + break; + case (MDOC_Eo): + break; + case (MDOC_Po): + /* FALLTHROUGH */ + case (MDOC_Pq): + term_word(p, "("); + break; + case (MDOC__T): + /* FALLTHROUGH */ + case (MDOC_Qo): + /* FALLTHROUGH */ + case (MDOC_Qq): + term_word(p, "\""); + break; + case (MDOC_Ql): + /* FALLTHROUGH */ + case (MDOC_So): + /* FALLTHROUGH */ + case (MDOC_Sq): + term_word(p, "`"); + break; + default: + abort(); + /* NOTREACHED */ + } + + p->flags |= TERMP_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static void +termp_quote_post(DECL_ARGS) +{ + + if (MDOC_BODY != n->type && MDOC_ELEM != n->type) + return; + + p->flags |= TERMP_NOSPACE; + + switch (n->tok) { + case (MDOC_Ao): + /* FALLTHROUGH */ + case (MDOC_Aq): + term_word(p, ">"); + break; + case (MDOC_Bro): + /* FALLTHROUGH */ + case (MDOC_Brq): + term_word(p, "}"); + break; + case (MDOC_Oo): + /* FALLTHROUGH */ + case (MDOC_Op): + /* FALLTHROUGH */ + case (MDOC_Bo): + /* FALLTHROUGH */ + case (MDOC_Bq): + term_word(p, "]"); + break; + case (MDOC_Do): + /* FALLTHROUGH */ + case (MDOC_Dq): + term_word(p, "''"); + break; + case (MDOC_Eo): + break; + case (MDOC_Po): + /* FALLTHROUGH */ + case (MDOC_Pq): + term_word(p, ")"); + break; + case (MDOC__T): + /* FALLTHROUGH */ + case (MDOC_Qo): + /* FALLTHROUGH */ + case (MDOC_Qq): + term_word(p, "\""); + break; + case (MDOC_Ql): + /* FALLTHROUGH */ + case (MDOC_So): + /* FALLTHROUGH */ + case (MDOC_Sq): + term_word(p, "'"); + break; + default: + abort(); + /* NOTREACHED */ + } +} + + +/* ARGSUSED */ +static int +termp_fo_pre(DECL_ARGS) +{ + + if (MDOC_BLOCK == n->type) { + synopsis_pre(p, n); + return(1); + } else if (MDOC_BODY == n->type) { + p->flags |= TERMP_NOSPACE; + term_word(p, "("); + p->flags |= TERMP_NOSPACE; + return(1); + } + + if (NULL == n->child) + return(0); + + /* XXX: we drop non-initial arguments as per groff. */ + + assert(n->child->string); + term_fontpush(p, TERMFONT_BOLD); + term_word(p, n->child->string); + return(0); +} + + +/* ARGSUSED */ +static void +termp_fo_post(DECL_ARGS) +{ + + if (MDOC_BODY != n->type) + return; + + p->flags |= TERMP_NOSPACE; + term_word(p, ")"); + + if (MDOC_SYNPRETTY & n->flags) { + p->flags |= TERMP_NOSPACE; + term_word(p, ";"); + } +} + + +/* ARGSUSED */ +static int +termp_bf_pre(DECL_ARGS) +{ + + if (MDOC_HEAD == n->type) + return(0); + else if (MDOC_BLOCK != n->type) + return(1); + + if (FONT_Em == n->norm->Bf.font) + term_fontpush(p, TERMFONT_UNDER); + else if (FONT_Sy == n->norm->Bf.font) + term_fontpush(p, TERMFONT_BOLD); + else + term_fontpush(p, TERMFONT_NONE); + + return(1); +} + + +/* ARGSUSED */ +static int +termp_sm_pre(DECL_ARGS) +{ + + assert(n->child && MDOC_TEXT == n->child->type); + if (0 == strcmp("on", n->child->string)) { + if (p->col) + p->flags &= ~TERMP_NOSPACE; + p->flags &= ~TERMP_NONOSPACE; + } else + p->flags |= TERMP_NONOSPACE; + + return(0); +} + + +/* ARGSUSED */ +static int +termp_ap_pre(DECL_ARGS) +{ + + p->flags |= TERMP_NOSPACE; + term_word(p, "'"); + p->flags |= TERMP_NOSPACE; + return(1); +} + + +/* ARGSUSED */ +static void +termp____post(DECL_ARGS) +{ + + /* + * Handle lists of authors. In general, print each followed by + * a comma. Don't print the comma if there are only two + * authors. + */ + if (MDOC__A == n->tok && n->next && MDOC__A == n->next->tok) + if (NULL == n->next->next || MDOC__A != n->next->next->tok) + if (NULL == n->prev || MDOC__A != n->prev->tok) + return; + + /* TODO: %U. */ + + if (NULL == n->parent || MDOC_Rs != n->parent->tok) + return; + + p->flags |= TERMP_NOSPACE; + if (NULL == n->next) { + term_word(p, "."); + p->flags |= TERMP_SENTENCE; + } else + term_word(p, ","); +} + + +/* ARGSUSED */ +static int +termp_li_pre(DECL_ARGS) +{ + + term_fontpush(p, TERMFONT_NONE); + return(1); +} + + +/* ARGSUSED */ +static int +termp_lk_pre(DECL_ARGS) +{ + const struct mdoc_node *nn, *sv; + + term_fontpush(p, TERMFONT_UNDER); + + nn = sv = n->child; + + if (NULL == nn || NULL == nn->next) + return(1); + + for (nn = nn->next; nn; nn = nn->next) + term_word(p, nn->string); + + term_fontpop(p); + + p->flags |= TERMP_NOSPACE; + term_word(p, ":"); + + term_fontpush(p, TERMFONT_BOLD); + term_word(p, sv->string); + term_fontpop(p); + + return(0); +} + + +/* ARGSUSED */ +static int +termp_bk_pre(DECL_ARGS) +{ + + switch (n->type) { + case (MDOC_BLOCK): + break; + case (MDOC_HEAD): + return(0); + case (MDOC_BODY): + if (n->parent->args || 0 == n->prev->nchild) + p->flags |= TERMP_PREKEEP; + break; + default: + abort(); + /* NOTREACHED */ + } + + return(1); +} + + +/* ARGSUSED */ +static void +termp_bk_post(DECL_ARGS) +{ + + if (MDOC_BODY == n->type) + p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP); +} + +/* ARGSUSED */ +static void +termp__t_post(DECL_ARGS) +{ + + /* + * If we're in an `Rs' and there's a journal present, then quote + * us instead of underlining us (for disambiguation). + */ + if (n->parent && MDOC_Rs == n->parent->tok && + n->parent->norm->Rs.quote_T) + termp_quote_post(p, pair, m, n); + + termp____post(p, pair, m, n); +} + +/* ARGSUSED */ +static int +termp__t_pre(DECL_ARGS) +{ + + /* + * If we're in an `Rs' and there's a journal present, then quote + * us instead of underlining us (for disambiguation). + */ + if (n->parent && MDOC_Rs == n->parent->tok && + n->parent->norm->Rs.quote_T) + return(termp_quote_pre(p, pair, m, n)); + + term_fontpush(p, TERMFONT_UNDER); + return(1); +} + +/* ARGSUSED */ +static int +termp_under_pre(DECL_ARGS) +{ + + term_fontpush(p, TERMFONT_UNDER); + return(1); +} diff --git a/mdoc_validate.c b/mdoc_validate.c new file mode 100644 index 000000000000..060ccdadec38 --- /dev/null +++ b/mdoc_validate.c @@ -0,0 +1,2403 @@ +/* $Id: mdoc_validate.c,v 1.182 2012/03/23 05:50:25 kristaps Exp $ */ +/* + * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * Copyright (c) 2010, 2011 Ingo Schwarze <schwarze@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifndef OSNAME +#include <sys/utsname.h> +#endif + +#include <sys/types.h> + +#include <assert.h> +#include <ctype.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "mdoc.h" +#include "mandoc.h" +#include "libmdoc.h" +#include "libmandoc.h" + +/* FIXME: .Bl -diag can't have non-text children in HEAD. */ + +#define PRE_ARGS struct mdoc *mdoc, struct mdoc_node *n +#define POST_ARGS struct mdoc *mdoc + +#define NUMSIZ 32 +#define DATESIZE 32 + +enum check_ineq { + CHECK_LT, + CHECK_GT, + CHECK_EQ +}; + +enum check_lvl { + CHECK_WARN, + CHECK_ERROR, +}; + +typedef int (*v_pre)(PRE_ARGS); +typedef int (*v_post)(POST_ARGS); + +struct valids { + v_pre *pre; + v_post *post; +}; + +static int check_count(struct mdoc *, enum mdoc_type, + enum check_lvl, enum check_ineq, int); +static int check_parent(PRE_ARGS, enum mdoct, enum mdoc_type); +static void check_text(struct mdoc *, int, int, char *); +static void check_argv(struct mdoc *, + struct mdoc_node *, struct mdoc_argv *); +static void check_args(struct mdoc *, struct mdoc_node *); +static int concat(char *, const struct mdoc_node *, size_t); +static enum mdoc_sec a2sec(const char *); +static size_t macro2len(enum mdoct); + +static int ebool(POST_ARGS); +static int berr_ge1(POST_ARGS); +static int bwarn_ge1(POST_ARGS); +static int ewarn_eq0(POST_ARGS); +static int ewarn_eq1(POST_ARGS); +static int ewarn_ge1(POST_ARGS); +static int ewarn_le1(POST_ARGS); +static int hwarn_eq0(POST_ARGS); +static int hwarn_eq1(POST_ARGS); +static int hwarn_ge1(POST_ARGS); +static int hwarn_le1(POST_ARGS); + +static int post_an(POST_ARGS); +static int post_at(POST_ARGS); +static int post_bf(POST_ARGS); +static int post_bl(POST_ARGS); +static int post_bl_block(POST_ARGS); +static int post_bl_block_width(POST_ARGS); +static int post_bl_block_tag(POST_ARGS); +static int post_bl_head(POST_ARGS); +static int post_bx(POST_ARGS); +static int post_dd(POST_ARGS); +static int post_dt(POST_ARGS); +static int post_defaults(POST_ARGS); +static int post_literal(POST_ARGS); +static int post_eoln(POST_ARGS); +static int post_it(POST_ARGS); +static int post_lb(POST_ARGS); +static int post_nm(POST_ARGS); +static int post_ns(POST_ARGS); +static int post_os(POST_ARGS); +static int post_ignpar(POST_ARGS); +static int post_prol(POST_ARGS); +static int post_root(POST_ARGS); +static int post_rs(POST_ARGS); +static int post_sh(POST_ARGS); +static int post_sh_body(POST_ARGS); +static int post_sh_head(POST_ARGS); +static int post_st(POST_ARGS); +static int post_std(POST_ARGS); +static int post_vt(POST_ARGS); +static int pre_an(PRE_ARGS); +static int pre_bd(PRE_ARGS); +static int pre_bl(PRE_ARGS); +static int pre_dd(PRE_ARGS); +static int pre_display(PRE_ARGS); +static int pre_dt(PRE_ARGS); +static int pre_it(PRE_ARGS); +static int pre_literal(PRE_ARGS); +static int pre_os(PRE_ARGS); +static int pre_par(PRE_ARGS); +static int pre_sh(PRE_ARGS); +static int pre_ss(PRE_ARGS); +static int pre_std(PRE_ARGS); + +static v_post posts_an[] = { post_an, NULL }; +static v_post posts_at[] = { post_at, post_defaults, NULL }; +static v_post posts_bd[] = { post_literal, hwarn_eq0, bwarn_ge1, NULL }; +static v_post posts_bf[] = { hwarn_le1, post_bf, NULL }; +static v_post posts_bk[] = { hwarn_eq0, bwarn_ge1, NULL }; +static v_post posts_bl[] = { bwarn_ge1, post_bl, NULL }; +static v_post posts_bx[] = { post_bx, NULL }; +static v_post posts_bool[] = { ebool, NULL }; +static v_post posts_eoln[] = { post_eoln, NULL }; +static v_post posts_defaults[] = { post_defaults, NULL }; +static v_post posts_dd[] = { post_dd, post_prol, NULL }; +static v_post posts_dl[] = { post_literal, bwarn_ge1, NULL }; +static v_post posts_dt[] = { post_dt, post_prol, NULL }; +static v_post posts_fo[] = { hwarn_eq1, bwarn_ge1, NULL }; +static v_post posts_it[] = { post_it, NULL }; +static v_post posts_lb[] = { post_lb, NULL }; +static v_post posts_nd[] = { berr_ge1, NULL }; +static v_post posts_nm[] = { post_nm, NULL }; +static v_post posts_notext[] = { ewarn_eq0, NULL }; +static v_post posts_ns[] = { post_ns, NULL }; +static v_post posts_os[] = { post_os, post_prol, NULL }; +static v_post posts_rs[] = { post_rs, NULL }; +static v_post posts_sh[] = { post_ignpar, hwarn_ge1, post_sh, NULL }; +static v_post posts_sp[] = { ewarn_le1, NULL }; +static v_post posts_ss[] = { post_ignpar, hwarn_ge1, NULL }; +static v_post posts_st[] = { post_st, NULL }; +static v_post posts_std[] = { post_std, NULL }; +static v_post posts_text[] = { ewarn_ge1, NULL }; +static v_post posts_text1[] = { ewarn_eq1, NULL }; +static v_post posts_vt[] = { post_vt, NULL }; +static v_post posts_wline[] = { bwarn_ge1, NULL }; +static v_pre pres_an[] = { pre_an, NULL }; +static v_pre pres_bd[] = { pre_display, pre_bd, pre_literal, pre_par, NULL }; +static v_pre pres_bl[] = { pre_bl, pre_par, NULL }; +static v_pre pres_d1[] = { pre_display, NULL }; +static v_pre pres_dl[] = { pre_literal, pre_display, NULL }; +static v_pre pres_dd[] = { pre_dd, NULL }; +static v_pre pres_dt[] = { pre_dt, NULL }; +static v_pre pres_er[] = { NULL, NULL }; +static v_pre pres_fd[] = { NULL, NULL }; +static v_pre pres_it[] = { pre_it, pre_par, NULL }; +static v_pre pres_os[] = { pre_os, NULL }; +static v_pre pres_pp[] = { pre_par, NULL }; +static v_pre pres_sh[] = { pre_sh, NULL }; +static v_pre pres_ss[] = { pre_ss, NULL }; +static v_pre pres_std[] = { pre_std, NULL }; + +static const struct valids mdoc_valids[MDOC_MAX] = { + { NULL, NULL }, /* Ap */ + { pres_dd, posts_dd }, /* Dd */ + { pres_dt, posts_dt }, /* Dt */ + { pres_os, posts_os }, /* Os */ + { pres_sh, posts_sh }, /* Sh */ + { pres_ss, posts_ss }, /* Ss */ + { pres_pp, posts_notext }, /* Pp */ + { pres_d1, posts_wline }, /* D1 */ + { pres_dl, posts_dl }, /* Dl */ + { pres_bd, posts_bd }, /* Bd */ + { NULL, NULL }, /* Ed */ + { pres_bl, posts_bl }, /* Bl */ + { NULL, NULL }, /* El */ + { pres_it, posts_it }, /* It */ + { NULL, NULL }, /* Ad */ + { pres_an, posts_an }, /* An */ + { NULL, posts_defaults }, /* Ar */ + { NULL, NULL }, /* Cd */ + { NULL, NULL }, /* Cm */ + { NULL, NULL }, /* Dv */ + { pres_er, NULL }, /* Er */ + { NULL, NULL }, /* Ev */ + { pres_std, posts_std }, /* Ex */ + { NULL, NULL }, /* Fa */ + { pres_fd, posts_text }, /* Fd */ + { NULL, NULL }, /* Fl */ + { NULL, NULL }, /* Fn */ + { NULL, NULL }, /* Ft */ + { NULL, NULL }, /* Ic */ + { NULL, posts_text1 }, /* In */ + { NULL, posts_defaults }, /* Li */ + { NULL, posts_nd }, /* Nd */ + { NULL, posts_nm }, /* Nm */ + { NULL, NULL }, /* Op */ + { NULL, NULL }, /* Ot */ + { NULL, posts_defaults }, /* Pa */ + { pres_std, posts_std }, /* Rv */ + { NULL, posts_st }, /* St */ + { NULL, NULL }, /* Va */ + { NULL, posts_vt }, /* Vt */ + { NULL, posts_text }, /* Xr */ + { NULL, posts_text }, /* %A */ + { NULL, posts_text }, /* %B */ /* FIXME: can be used outside Rs/Re. */ + { NULL, posts_text }, /* %D */ + { NULL, posts_text }, /* %I */ + { NULL, posts_text }, /* %J */ + { NULL, posts_text }, /* %N */ + { NULL, posts_text }, /* %O */ + { NULL, posts_text }, /* %P */ + { NULL, posts_text }, /* %R */ + { NULL, posts_text }, /* %T */ /* FIXME: can be used outside Rs/Re. */ + { NULL, posts_text }, /* %V */ + { NULL, NULL }, /* Ac */ + { NULL, NULL }, /* Ao */ + { NULL, NULL }, /* Aq */ + { NULL, posts_at }, /* At */ + { NULL, NULL }, /* Bc */ + { NULL, posts_bf }, /* Bf */ + { NULL, NULL }, /* Bo */ + { NULL, NULL }, /* Bq */ + { NULL, NULL }, /* Bsx */ + { NULL, posts_bx }, /* Bx */ + { NULL, posts_bool }, /* Db */ + { NULL, NULL }, /* Dc */ + { NULL, NULL }, /* Do */ + { NULL, NULL }, /* Dq */ + { NULL, NULL }, /* Ec */ + { NULL, NULL }, /* Ef */ + { NULL, NULL }, /* Em */ + { NULL, NULL }, /* Eo */ + { NULL, NULL }, /* Fx */ + { NULL, NULL }, /* Ms */ + { NULL, posts_notext }, /* No */ + { NULL, posts_ns }, /* Ns */ + { NULL, NULL }, /* Nx */ + { NULL, NULL }, /* Ox */ + { NULL, NULL }, /* Pc */ + { NULL, posts_text1 }, /* Pf */ + { NULL, NULL }, /* Po */ + { NULL, NULL }, /* Pq */ + { NULL, NULL }, /* Qc */ + { NULL, NULL }, /* Ql */ + { NULL, NULL }, /* Qo */ + { NULL, NULL }, /* Qq */ + { NULL, NULL }, /* Re */ + { NULL, posts_rs }, /* Rs */ + { NULL, NULL }, /* Sc */ + { NULL, NULL }, /* So */ + { NULL, NULL }, /* Sq */ + { NULL, posts_bool }, /* Sm */ + { NULL, NULL }, /* Sx */ + { NULL, NULL }, /* Sy */ + { NULL, NULL }, /* Tn */ + { NULL, NULL }, /* Ux */ + { NULL, NULL }, /* Xc */ + { NULL, NULL }, /* Xo */ + { NULL, posts_fo }, /* Fo */ + { NULL, NULL }, /* Fc */ + { NULL, NULL }, /* Oo */ + { NULL, NULL }, /* Oc */ + { NULL, posts_bk }, /* Bk */ + { NULL, NULL }, /* Ek */ + { NULL, posts_eoln }, /* Bt */ + { NULL, NULL }, /* Hf */ + { NULL, NULL }, /* Fr */ + { NULL, posts_eoln }, /* Ud */ + { NULL, posts_lb }, /* Lb */ + { NULL, posts_notext }, /* Lp */ + { NULL, NULL }, /* Lk */ + { NULL, posts_defaults }, /* Mt */ + { NULL, NULL }, /* Brq */ + { NULL, NULL }, /* Bro */ + { NULL, NULL }, /* Brc */ + { NULL, posts_text }, /* %C */ + { NULL, NULL }, /* Es */ + { NULL, NULL }, /* En */ + { NULL, NULL }, /* Dx */ + { NULL, posts_text }, /* %Q */ + { NULL, posts_notext }, /* br */ + { pres_pp, posts_sp }, /* sp */ + { NULL, posts_text1 }, /* %U */ + { NULL, NULL }, /* Ta */ +}; + +#define RSORD_MAX 14 /* Number of `Rs' blocks. */ + +static const enum mdoct rsord[RSORD_MAX] = { + MDOC__A, + MDOC__T, + MDOC__B, + MDOC__I, + MDOC__J, + MDOC__R, + MDOC__N, + MDOC__V, + MDOC__P, + MDOC__Q, + MDOC__D, + MDOC__O, + MDOC__C, + MDOC__U +}; + +static const char * const secnames[SEC__MAX] = { + NULL, + "NAME", + "LIBRARY", + "SYNOPSIS", + "DESCRIPTION", + "IMPLEMENTATION NOTES", + "RETURN VALUES", + "ENVIRONMENT", + "FILES", + "EXIT STATUS", + "EXAMPLES", + "DIAGNOSTICS", + "COMPATIBILITY", + "ERRORS", + "SEE ALSO", + "STANDARDS", + "HISTORY", + "AUTHORS", + "CAVEATS", + "BUGS", + "SECURITY CONSIDERATIONS", + NULL +}; + +int +mdoc_valid_pre(struct mdoc *mdoc, struct mdoc_node *n) +{ + v_pre *p; + int line, pos; + char *tp; + + switch (n->type) { + case (MDOC_TEXT): + tp = n->string; + line = n->line; + pos = n->pos; + check_text(mdoc, line, pos, tp); + /* FALLTHROUGH */ + case (MDOC_TBL): + /* FALLTHROUGH */ + case (MDOC_EQN): + /* FALLTHROUGH */ + case (MDOC_ROOT): + return(1); + default: + break; + } + + check_args(mdoc, n); + + if (NULL == mdoc_valids[n->tok].pre) + return(1); + for (p = mdoc_valids[n->tok].pre; *p; p++) + if ( ! (*p)(mdoc, n)) + return(0); + return(1); +} + + +int +mdoc_valid_post(struct mdoc *mdoc) +{ + v_post *p; + + if (MDOC_VALID & mdoc->last->flags) + return(1); + mdoc->last->flags |= MDOC_VALID; + + switch (mdoc->last->type) { + case (MDOC_TEXT): + /* FALLTHROUGH */ + case (MDOC_EQN): + /* FALLTHROUGH */ + case (MDOC_TBL): + return(1); + case (MDOC_ROOT): + return(post_root(mdoc)); + default: + break; + } + + if (NULL == mdoc_valids[mdoc->last->tok].post) + return(1); + for (p = mdoc_valids[mdoc->last->tok].post; *p; p++) + if ( ! (*p)(mdoc)) + return(0); + + return(1); +} + +static int +check_count(struct mdoc *m, enum mdoc_type type, + enum check_lvl lvl, enum check_ineq ineq, int val) +{ + const char *p; + enum mandocerr t; + + if (m->last->type != type) + return(1); + + switch (ineq) { + case (CHECK_LT): + p = "less than "; + if (m->last->nchild < val) + return(1); + break; + case (CHECK_GT): + p = "more than "; + if (m->last->nchild > val) + return(1); + break; + case (CHECK_EQ): + p = ""; + if (val == m->last->nchild) + return(1); + break; + default: + abort(); + /* NOTREACHED */ + } + + t = lvl == CHECK_WARN ? MANDOCERR_ARGCWARN : MANDOCERR_ARGCOUNT; + mandoc_vmsg(t, m->parse, m->last->line, m->last->pos, + "want %s%d children (have %d)", + p, val, m->last->nchild); + return(1); +} + +static int +berr_ge1(POST_ARGS) +{ + + return(check_count(mdoc, MDOC_BODY, CHECK_ERROR, CHECK_GT, 0)); +} + +static int +bwarn_ge1(POST_ARGS) +{ + return(check_count(mdoc, MDOC_BODY, CHECK_WARN, CHECK_GT, 0)); +} + +static int +ewarn_eq0(POST_ARGS) +{ + return(check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_EQ, 0)); +} + +static int +ewarn_eq1(POST_ARGS) +{ + return(check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_EQ, 1)); +} + +static int +ewarn_ge1(POST_ARGS) +{ + return(check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_GT, 0)); +} + +static int +ewarn_le1(POST_ARGS) +{ + return(check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_LT, 2)); +} + +static int +hwarn_eq0(POST_ARGS) +{ + return(check_count(mdoc, MDOC_HEAD, CHECK_WARN, CHECK_EQ, 0)); +} + +static int +hwarn_eq1(POST_ARGS) +{ + return(check_count(mdoc, MDOC_HEAD, CHECK_WARN, CHECK_EQ, 1)); +} + +static int +hwarn_ge1(POST_ARGS) +{ + return(check_count(mdoc, MDOC_HEAD, CHECK_WARN, CHECK_GT, 0)); +} + +static int +hwarn_le1(POST_ARGS) +{ + return(check_count(mdoc, MDOC_HEAD, CHECK_WARN, CHECK_LT, 2)); +} + +static void +check_args(struct mdoc *m, struct mdoc_node *n) +{ + int i; + + if (NULL == n->args) + return; + + assert(n->args->argc); + for (i = 0; i < (int)n->args->argc; i++) + check_argv(m, n, &n->args->argv[i]); +} + +static void +check_argv(struct mdoc *m, struct mdoc_node *n, struct mdoc_argv *v) +{ + int i; + + for (i = 0; i < (int)v->sz; i++) + check_text(m, v->line, v->pos, v->value[i]); + + /* FIXME: move to post_std(). */ + + if (MDOC_Std == v->arg) + if ( ! (v->sz || m->meta.name)) + mdoc_nmsg(m, n, MANDOCERR_NONAME); +} + +static void +check_text(struct mdoc *m, int ln, int pos, char *p) +{ + char *cp; + + if (MDOC_LITERAL & m->flags) + return; + + for (cp = p; NULL != (p = strchr(p, '\t')); p++) + mdoc_pmsg(m, ln, pos + (int)(p - cp), MANDOCERR_BADTAB); +} + +static int +check_parent(PRE_ARGS, enum mdoct tok, enum mdoc_type t) +{ + + assert(n->parent); + if ((MDOC_ROOT == t || tok == n->parent->tok) && + (t == n->parent->type)) + return(1); + + mandoc_vmsg(MANDOCERR_SYNTCHILD, mdoc->parse, n->line, + n->pos, "want parent %s", MDOC_ROOT == t ? + "<root>" : mdoc_macronames[tok]); + return(0); +} + + +static int +pre_display(PRE_ARGS) +{ + struct mdoc_node *node; + + if (MDOC_BLOCK != n->type) + return(1); + + for (node = mdoc->last->parent; node; node = node->parent) + if (MDOC_BLOCK == node->type) + if (MDOC_Bd == node->tok) + break; + + if (node) + mdoc_nmsg(mdoc, n, MANDOCERR_NESTEDDISP); + + return(1); +} + + +static int +pre_bl(PRE_ARGS) +{ + int i, comp, dup; + const char *offs, *width; + enum mdoc_list lt; + struct mdoc_node *np; + + if (MDOC_BLOCK != n->type) { + if (ENDBODY_NOT != n->end) { + assert(n->pending); + np = n->pending->parent; + } else + np = n->parent; + + assert(np); + assert(MDOC_BLOCK == np->type); + assert(MDOC_Bl == np->tok); + return(1); + } + + /* + * First figure out which kind of list to use: bind ourselves to + * the first mentioned list type and warn about any remaining + * ones. If we find no list type, we default to LIST_item. + */ + + /* LINTED */ + for (i = 0; n->args && i < (int)n->args->argc; i++) { + lt = LIST__NONE; + dup = comp = 0; + width = offs = NULL; + switch (n->args->argv[i].arg) { + /* Set list types. */ + case (MDOC_Bullet): + lt = LIST_bullet; + break; + case (MDOC_Dash): + lt = LIST_dash; + break; + case (MDOC_Enum): + lt = LIST_enum; + break; + case (MDOC_Hyphen): + lt = LIST_hyphen; + break; + case (MDOC_Item): + lt = LIST_item; + break; + case (MDOC_Tag): + lt = LIST_tag; + break; + case (MDOC_Diag): + lt = LIST_diag; + break; + case (MDOC_Hang): + lt = LIST_hang; + break; + case (MDOC_Ohang): + lt = LIST_ohang; + break; + case (MDOC_Inset): + lt = LIST_inset; + break; + case (MDOC_Column): + lt = LIST_column; + break; + /* Set list arguments. */ + case (MDOC_Compact): + dup = n->norm->Bl.comp; + comp = 1; + break; + case (MDOC_Width): + /* NB: this can be empty! */ + if (n->args->argv[i].sz) { + width = n->args->argv[i].value[0]; + dup = (NULL != n->norm->Bl.width); + break; + } + mdoc_nmsg(mdoc, n, MANDOCERR_IGNARGV); + break; + case (MDOC_Offset): + /* NB: this can be empty! */ + if (n->args->argv[i].sz) { + offs = n->args->argv[i].value[0]; + dup = (NULL != n->norm->Bl.offs); + break; + } + mdoc_nmsg(mdoc, n, MANDOCERR_IGNARGV); + break; + default: + continue; + } + + /* Check: duplicate auxiliary arguments. */ + + if (dup) + mdoc_nmsg(mdoc, n, MANDOCERR_ARGVREP); + + if (comp && ! dup) + n->norm->Bl.comp = comp; + if (offs && ! dup) + n->norm->Bl.offs = offs; + if (width && ! dup) + n->norm->Bl.width = width; + + /* Check: multiple list types. */ + + if (LIST__NONE != lt && n->norm->Bl.type != LIST__NONE) + mdoc_nmsg(mdoc, n, MANDOCERR_LISTREP); + + /* Assign list type. */ + + if (LIST__NONE != lt && n->norm->Bl.type == LIST__NONE) { + n->norm->Bl.type = lt; + /* Set column information, too. */ + if (LIST_column == lt) { + n->norm->Bl.ncols = + n->args->argv[i].sz; + n->norm->Bl.cols = (void *) + n->args->argv[i].value; + } + } + + /* The list type should come first. */ + + if (n->norm->Bl.type == LIST__NONE) + if (n->norm->Bl.width || + n->norm->Bl.offs || + n->norm->Bl.comp) + mdoc_nmsg(mdoc, n, MANDOCERR_LISTFIRST); + + continue; + } + + /* Allow lists to default to LIST_item. */ + + if (LIST__NONE == n->norm->Bl.type) { + mdoc_nmsg(mdoc, n, MANDOCERR_LISTTYPE); + n->norm->Bl.type = LIST_item; + } + + /* + * Validate the width field. Some list types don't need width + * types and should be warned about them. Others should have it + * and must also be warned. + */ + + switch (n->norm->Bl.type) { + case (LIST_tag): + if (n->norm->Bl.width) + break; + mdoc_nmsg(mdoc, n, MANDOCERR_NOWIDTHARG); + break; + case (LIST_column): + /* FALLTHROUGH */ + case (LIST_diag): + /* FALLTHROUGH */ + case (LIST_ohang): + /* FALLTHROUGH */ + case (LIST_inset): + /* FALLTHROUGH */ + case (LIST_item): + if (n->norm->Bl.width) + mdoc_nmsg(mdoc, n, MANDOCERR_IGNARGV); + break; + default: + break; + } + + return(1); +} + + +static int +pre_bd(PRE_ARGS) +{ + int i, dup, comp; + enum mdoc_disp dt; + const char *offs; + struct mdoc_node *np; + + if (MDOC_BLOCK != n->type) { + if (ENDBODY_NOT != n->end) { + assert(n->pending); + np = n->pending->parent; + } else + np = n->parent; + + assert(np); + assert(MDOC_BLOCK == np->type); + assert(MDOC_Bd == np->tok); + return(1); + } + + /* LINTED */ + for (i = 0; n->args && i < (int)n->args->argc; i++) { + dt = DISP__NONE; + dup = comp = 0; + offs = NULL; + + switch (n->args->argv[i].arg) { + case (MDOC_Centred): + dt = DISP_centred; + break; + case (MDOC_Ragged): + dt = DISP_ragged; + break; + case (MDOC_Unfilled): + dt = DISP_unfilled; + break; + case (MDOC_Filled): + dt = DISP_filled; + break; + case (MDOC_Literal): + dt = DISP_literal; + break; + case (MDOC_File): + mdoc_nmsg(mdoc, n, MANDOCERR_BADDISP); + return(0); + case (MDOC_Offset): + /* NB: this can be empty! */ + if (n->args->argv[i].sz) { + offs = n->args->argv[i].value[0]; + dup = (NULL != n->norm->Bd.offs); + break; + } + mdoc_nmsg(mdoc, n, MANDOCERR_IGNARGV); + break; + case (MDOC_Compact): + comp = 1; + dup = n->norm->Bd.comp; + break; + default: + abort(); + /* NOTREACHED */ + } + + /* Check whether we have duplicates. */ + + if (dup) + mdoc_nmsg(mdoc, n, MANDOCERR_ARGVREP); + + /* Make our auxiliary assignments. */ + + if (offs && ! dup) + n->norm->Bd.offs = offs; + if (comp && ! dup) + n->norm->Bd.comp = comp; + + /* Check whether a type has already been assigned. */ + + if (DISP__NONE != dt && n->norm->Bd.type != DISP__NONE) + mdoc_nmsg(mdoc, n, MANDOCERR_DISPREP); + + /* Make our type assignment. */ + + if (DISP__NONE != dt && n->norm->Bd.type == DISP__NONE) + n->norm->Bd.type = dt; + } + + if (DISP__NONE == n->norm->Bd.type) { + mdoc_nmsg(mdoc, n, MANDOCERR_DISPTYPE); + n->norm->Bd.type = DISP_ragged; + } + + return(1); +} + + +static int +pre_ss(PRE_ARGS) +{ + + if (MDOC_BLOCK != n->type) + return(1); + return(check_parent(mdoc, n, MDOC_Sh, MDOC_BODY)); +} + + +static int +pre_sh(PRE_ARGS) +{ + + if (MDOC_BLOCK != n->type) + return(1); + + roff_regunset(mdoc->roff, REG_nS); + return(check_parent(mdoc, n, MDOC_MAX, MDOC_ROOT)); +} + + +static int +pre_it(PRE_ARGS) +{ + + if (MDOC_BLOCK != n->type) + return(1); + + return(check_parent(mdoc, n, MDOC_Bl, MDOC_BODY)); +} + + +static int +pre_an(PRE_ARGS) +{ + int i; + + if (NULL == n->args) + return(1); + + for (i = 1; i < (int)n->args->argc; i++) + mdoc_pmsg(mdoc, n->args->argv[i].line, + n->args->argv[i].pos, MANDOCERR_IGNARGV); + + if (MDOC_Split == n->args->argv[0].arg) + n->norm->An.auth = AUTH_split; + else if (MDOC_Nosplit == n->args->argv[0].arg) + n->norm->An.auth = AUTH_nosplit; + else + abort(); + + return(1); +} + +static int +pre_std(PRE_ARGS) +{ + + if (n->args && 1 == n->args->argc) + if (MDOC_Std == n->args->argv[0].arg) + return(1); + + mdoc_nmsg(mdoc, n, MANDOCERR_NOARGV); + return(1); +} + +static int +pre_dt(PRE_ARGS) +{ + + if (NULL == mdoc->meta.date || mdoc->meta.os) + mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGOOO); + + if (mdoc->meta.title) + mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGREP); + + return(1); +} + +static int +pre_os(PRE_ARGS) +{ + + if (NULL == mdoc->meta.title || NULL == mdoc->meta.date) + mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGOOO); + + if (mdoc->meta.os) + mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGREP); + + return(1); +} + +static int +pre_dd(PRE_ARGS) +{ + + if (mdoc->meta.title || mdoc->meta.os) + mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGOOO); + + if (mdoc->meta.date) + mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGREP); + + return(1); +} + + +static int +post_bf(POST_ARGS) +{ + struct mdoc_node *np; + enum mdocargt arg; + + /* + * Unlike other data pointers, these are "housed" by the HEAD + * element, which contains the goods. + */ + + if (MDOC_HEAD != mdoc->last->type) { + if (ENDBODY_NOT != mdoc->last->end) { + assert(mdoc->last->pending); + np = mdoc->last->pending->parent->head; + } else if (MDOC_BLOCK != mdoc->last->type) { + np = mdoc->last->parent->head; + } else + np = mdoc->last->head; + + assert(np); + assert(MDOC_HEAD == np->type); + assert(MDOC_Bf == np->tok); + return(1); + } + + np = mdoc->last; + assert(MDOC_BLOCK == np->parent->type); + assert(MDOC_Bf == np->parent->tok); + + /* + * Cannot have both argument and parameter. + * If neither is specified, let it through with a warning. + */ + + if (np->parent->args && np->child) { + mdoc_nmsg(mdoc, np, MANDOCERR_SYNTARGVCOUNT); + return(0); + } else if (NULL == np->parent->args && NULL == np->child) { + mdoc_nmsg(mdoc, np, MANDOCERR_FONTTYPE); + return(1); + } + + /* Extract argument into data. */ + + if (np->parent->args) { + arg = np->parent->args->argv[0].arg; + if (MDOC_Emphasis == arg) + np->norm->Bf.font = FONT_Em; + else if (MDOC_Literal == arg) + np->norm->Bf.font = FONT_Li; + else if (MDOC_Symbolic == arg) + np->norm->Bf.font = FONT_Sy; + else + abort(); + return(1); + } + + /* Extract parameter into data. */ + + if (0 == strcmp(np->child->string, "Em")) + np->norm->Bf.font = FONT_Em; + else if (0 == strcmp(np->child->string, "Li")) + np->norm->Bf.font = FONT_Li; + else if (0 == strcmp(np->child->string, "Sy")) + np->norm->Bf.font = FONT_Sy; + else + mdoc_nmsg(mdoc, np, MANDOCERR_FONTTYPE); + + return(1); +} + +static int +post_lb(POST_ARGS) +{ + const char *p; + char *buf; + size_t sz; + + check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_EQ, 1); + + assert(mdoc->last->child); + assert(MDOC_TEXT == mdoc->last->child->type); + + p = mdoc_a2lib(mdoc->last->child->string); + + /* If lookup ok, replace with table value. */ + + if (p) { + free(mdoc->last->child->string); + mdoc->last->child->string = mandoc_strdup(p); + return(1); + } + + /* If not, use "library ``xxxx''. */ + + sz = strlen(mdoc->last->child->string) + + 2 + strlen("\\(lqlibrary\\(rq"); + buf = mandoc_malloc(sz); + snprintf(buf, sz, "library \\(lq%s\\(rq", + mdoc->last->child->string); + free(mdoc->last->child->string); + mdoc->last->child->string = buf; + return(1); +} + +static int +post_eoln(POST_ARGS) +{ + + if (mdoc->last->child) + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_ARGSLOST); + return(1); +} + + +static int +post_vt(POST_ARGS) +{ + const struct mdoc_node *n; + + /* + * The Vt macro comes in both ELEM and BLOCK form, both of which + * have different syntaxes (yet more context-sensitive + * behaviour). ELEM types must have a child, which is already + * guaranteed by the in_line parsing routine; BLOCK types, + * specifically the BODY, should only have TEXT children. + */ + + if (MDOC_BODY != mdoc->last->type) + return(1); + + for (n = mdoc->last->child; n; n = n->next) + if (MDOC_TEXT != n->type) + mdoc_nmsg(mdoc, n, MANDOCERR_CHILD); + + return(1); +} + + +static int +post_nm(POST_ARGS) +{ + char buf[BUFSIZ]; + int c; + + /* If no child specified, make sure we have the meta name. */ + + if (NULL == mdoc->last->child && NULL == mdoc->meta.name) { + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NONAME); + return(1); + } else if (mdoc->meta.name) + return(1); + + /* If no meta name, set it from the child. */ + + buf[0] = '\0'; + if (-1 == (c = concat(buf, mdoc->last->child, BUFSIZ))) { + mdoc_nmsg(mdoc, mdoc->last->child, MANDOCERR_MEM); + return(0); + } + + assert(c); + mdoc->meta.name = mandoc_strdup(buf); + return(1); +} + +static int +post_literal(POST_ARGS) +{ + + /* + * The `Dl' (note "el" not "one") and `Bd' macros unset the + * MDOC_LITERAL flag as they leave. Note that `Bd' only sets + * this in literal mode, but it doesn't hurt to just switch it + * off in general since displays can't be nested. + */ + + if (MDOC_BODY == mdoc->last->type) + mdoc->flags &= ~MDOC_LITERAL; + + return(1); +} + +static int +post_defaults(POST_ARGS) +{ + struct mdoc_node *nn; + + /* + * The `Ar' defaults to "file ..." if no value is provided as an + * argument; the `Mt' and `Pa' macros use "~"; the `Li' just + * gets an empty string. + */ + + if (mdoc->last->child) + return(1); + + nn = mdoc->last; + mdoc->next = MDOC_NEXT_CHILD; + + switch (nn->tok) { + case (MDOC_Ar): + if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, "file")) + return(0); + if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, "...")) + return(0); + break; + case (MDOC_At): + if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, "AT&T")) + return(0); + if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, "UNIX")) + return(0); + break; + case (MDOC_Li): + if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, "")) + return(0); + break; + case (MDOC_Pa): + /* FALLTHROUGH */ + case (MDOC_Mt): + if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, "~")) + return(0); + break; + default: + abort(); + /* NOTREACHED */ + } + + mdoc->last = nn; + return(1); +} + +static int +post_at(POST_ARGS) +{ + const char *p, *q; + char *buf; + size_t sz; + + /* + * If we have a child, look it up in the standard keys. If a + * key exist, use that instead of the child; if it doesn't, + * prefix "AT&T UNIX " to the existing data. + */ + + if (NULL == mdoc->last->child) + return(1); + + assert(MDOC_TEXT == mdoc->last->child->type); + p = mdoc_a2att(mdoc->last->child->string); + + if (p) { + free(mdoc->last->child->string); + mdoc->last->child->string = mandoc_strdup(p); + } else { + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADATT); + p = "AT&T UNIX "; + q = mdoc->last->child->string; + sz = strlen(p) + strlen(q) + 1; + buf = mandoc_malloc(sz); + strlcpy(buf, p, sz); + strlcat(buf, q, sz); + free(mdoc->last->child->string); + mdoc->last->child->string = buf; + } + + return(1); +} + +static int +post_an(POST_ARGS) +{ + struct mdoc_node *np; + + np = mdoc->last; + if (AUTH__NONE == np->norm->An.auth) { + if (0 == np->child) + check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_GT, 0); + } else if (np->child) + check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_EQ, 0); + + return(1); +} + + +static int +post_it(POST_ARGS) +{ + int i, cols; + enum mdoc_list lt; + struct mdoc_node *n, *c; + enum mandocerr er; + + if (MDOC_BLOCK != mdoc->last->type) + return(1); + + n = mdoc->last->parent->parent; + lt = n->norm->Bl.type; + + if (LIST__NONE == lt) { + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_LISTTYPE); + return(1); + } + + switch (lt) { + case (LIST_tag): + if (mdoc->last->head->child) + break; + /* FIXME: give this a dummy value. */ + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NOARGS); + break; + case (LIST_hang): + /* FALLTHROUGH */ + case (LIST_ohang): + /* FALLTHROUGH */ + case (LIST_inset): + /* FALLTHROUGH */ + case (LIST_diag): + if (NULL == mdoc->last->head->child) + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NOARGS); + break; + case (LIST_bullet): + /* FALLTHROUGH */ + case (LIST_dash): + /* FALLTHROUGH */ + case (LIST_enum): + /* FALLTHROUGH */ + case (LIST_hyphen): + if (NULL == mdoc->last->body->child) + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NOBODY); + /* FALLTHROUGH */ + case (LIST_item): + if (mdoc->last->head->child) + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_ARGSLOST); + break; + case (LIST_column): + cols = (int)n->norm->Bl.ncols; + + assert(NULL == mdoc->last->head->child); + + if (NULL == mdoc->last->body->child) + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NOBODY); + + for (i = 0, c = mdoc->last->child; c; c = c->next) + if (MDOC_BODY == c->type) + i++; + + if (i < cols) + er = MANDOCERR_ARGCOUNT; + else if (i == cols || i == cols + 1) + break; + else + er = MANDOCERR_SYNTARGCOUNT; + + mandoc_vmsg(er, mdoc->parse, mdoc->last->line, + mdoc->last->pos, + "columns == %d (have %d)", cols, i); + return(MANDOCERR_ARGCOUNT == er); + default: + break; + } + + return(1); +} + +static int +post_bl_block(POST_ARGS) +{ + struct mdoc_node *n; + + /* + * These are fairly complicated, so we've broken them into two + * functions. post_bl_block_tag() is called when a -tag is + * specified, but no -width (it must be guessed). The second + * when a -width is specified (macro indicators must be + * rewritten into real lengths). + */ + + n = mdoc->last; + + if (LIST_tag == n->norm->Bl.type && + NULL == n->norm->Bl.width) { + if ( ! post_bl_block_tag(mdoc)) + return(0); + } else if (NULL != n->norm->Bl.width) { + if ( ! post_bl_block_width(mdoc)) + return(0); + } else + return(1); + + assert(n->norm->Bl.width); + return(1); +} + +static int +post_bl_block_width(POST_ARGS) +{ + size_t width; + int i; + enum mdoct tok; + struct mdoc_node *n; + char buf[NUMSIZ]; + + n = mdoc->last; + + /* + * Calculate the real width of a list from the -width string, + * which may contain a macro (with a known default width), a + * literal string, or a scaling width. + * + * If the value to -width is a macro, then we re-write it to be + * the macro's width as set in share/tmac/mdoc/doc-common. + */ + + if (0 == strcmp(n->norm->Bl.width, "Ds")) + width = 6; + else if (MDOC_MAX == (tok = mdoc_hash_find(n->norm->Bl.width))) + return(1); + else if (0 == (width = macro2len(tok))) { + mdoc_nmsg(mdoc, n, MANDOCERR_BADWIDTH); + return(1); + } + + /* The value already exists: free and reallocate it. */ + + assert(n->args); + + for (i = 0; i < (int)n->args->argc; i++) + if (MDOC_Width == n->args->argv[i].arg) + break; + + assert(i < (int)n->args->argc); + + snprintf(buf, NUMSIZ, "%un", (unsigned int)width); + free(n->args->argv[i].value[0]); + n->args->argv[i].value[0] = mandoc_strdup(buf); + + /* Set our width! */ + n->norm->Bl.width = n->args->argv[i].value[0]; + return(1); +} + +static int +post_bl_block_tag(POST_ARGS) +{ + struct mdoc_node *n, *nn; + size_t sz, ssz; + int i; + char buf[NUMSIZ]; + + /* + * Calculate the -width for a `Bl -tag' list if it hasn't been + * provided. Uses the first head macro. NOTE AGAIN: this is + * ONLY if the -width argument has NOT been provided. See + * post_bl_block_width() for converting the -width string. + */ + + sz = 10; + n = mdoc->last; + + for (nn = n->body->child; nn; nn = nn->next) { + if (MDOC_It != nn->tok) + continue; + + assert(MDOC_BLOCK == nn->type); + nn = nn->head->child; + + if (nn == NULL) + break; + + if (MDOC_TEXT == nn->type) { + sz = strlen(nn->string) + 1; + break; + } + + if (0 != (ssz = macro2len(nn->tok))) + sz = ssz; + + break; + } + + /* Defaults to ten ens. */ + + snprintf(buf, NUMSIZ, "%un", (unsigned int)sz); + + /* + * We have to dynamically add this to the macro's argument list. + * We're guaranteed that a MDOC_Width doesn't already exist. + */ + + assert(n->args); + i = (int)(n->args->argc)++; + + n->args->argv = mandoc_realloc(n->args->argv, + n->args->argc * sizeof(struct mdoc_argv)); + + n->args->argv[i].arg = MDOC_Width; + n->args->argv[i].line = n->line; + n->args->argv[i].pos = n->pos; + n->args->argv[i].sz = 1; + n->args->argv[i].value = mandoc_malloc(sizeof(char *)); + n->args->argv[i].value[0] = mandoc_strdup(buf); + + /* Set our width! */ + n->norm->Bl.width = n->args->argv[i].value[0]; + return(1); +} + + +static int +post_bl_head(POST_ARGS) +{ + struct mdoc_node *np, *nn, *nnp; + int i, j; + + if (LIST_column != mdoc->last->norm->Bl.type) + /* FIXME: this should be ERROR class... */ + return(hwarn_eq0(mdoc)); + + /* + * Convert old-style lists, where the column width specifiers + * trail as macro parameters, to the new-style ("normal-form") + * lists where they're argument values following -column. + */ + + /* First, disallow both types and allow normal-form. */ + + /* + * TODO: technically, we can accept both and just merge the two + * lists, but I'll leave that for another day. + */ + + if (mdoc->last->norm->Bl.ncols && mdoc->last->nchild) { + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_COLUMNS); + return(0); + } else if (NULL == mdoc->last->child) + return(1); + + np = mdoc->last->parent; + assert(np->args); + + for (j = 0; j < (int)np->args->argc; j++) + if (MDOC_Column == np->args->argv[j].arg) + break; + + assert(j < (int)np->args->argc); + assert(0 == np->args->argv[j].sz); + + /* + * Accommodate for new-style groff column syntax. Shuffle the + * child nodes, all of which must be TEXT, as arguments for the + * column field. Then, delete the head children. + */ + + np->args->argv[j].sz = (size_t)mdoc->last->nchild; + np->args->argv[j].value = mandoc_malloc + ((size_t)mdoc->last->nchild * sizeof(char *)); + + mdoc->last->norm->Bl.ncols = np->args->argv[j].sz; + mdoc->last->norm->Bl.cols = (void *)np->args->argv[j].value; + + for (i = 0, nn = mdoc->last->child; nn; i++) { + np->args->argv[j].value[i] = nn->string; + nn->string = NULL; + nnp = nn; + nn = nn->next; + mdoc_node_delete(NULL, nnp); + } + + mdoc->last->nchild = 0; + mdoc->last->child = NULL; + + return(1); +} + +static int +post_bl(POST_ARGS) +{ + struct mdoc_node *n; + + if (MDOC_HEAD == mdoc->last->type) + return(post_bl_head(mdoc)); + if (MDOC_BLOCK == mdoc->last->type) + return(post_bl_block(mdoc)); + if (MDOC_BODY != mdoc->last->type) + return(1); + + for (n = mdoc->last->child; n; n = n->next) { + switch (n->tok) { + case (MDOC_Lp): + /* FALLTHROUGH */ + case (MDOC_Pp): + mdoc_nmsg(mdoc, n, MANDOCERR_CHILD); + /* FALLTHROUGH */ + case (MDOC_It): + /* FALLTHROUGH */ + case (MDOC_Sm): + continue; + default: + break; + } + + mdoc_nmsg(mdoc, n, MANDOCERR_SYNTCHILD); + return(0); + } + + return(1); +} + +static int +ebool(struct mdoc *mdoc) +{ + + if (NULL == mdoc->last->child) { + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_MACROEMPTY); + mdoc_node_delete(mdoc, mdoc->last); + return(1); + } + check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_EQ, 1); + + assert(MDOC_TEXT == mdoc->last->child->type); + + if (0 == strcmp(mdoc->last->child->string, "on")) + return(1); + if (0 == strcmp(mdoc->last->child->string, "off")) + return(1); + + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADBOOL); + return(1); +} + +static int +post_root(POST_ARGS) +{ + int erc; + struct mdoc_node *n; + + erc = 0; + + /* Check that we have a finished prologue. */ + + if ( ! (MDOC_PBODY & mdoc->flags)) { + erc++; + mdoc_nmsg(mdoc, mdoc->first, MANDOCERR_NODOCPROLOG); + } + + n = mdoc->first; + assert(n); + + /* Check that we begin with a proper `Sh'. */ + + if (NULL == n->child) { + erc++; + mdoc_nmsg(mdoc, n, MANDOCERR_NODOCBODY); + } else if (MDOC_BLOCK != n->child->type || + MDOC_Sh != n->child->tok) { + erc++; + /* Can this be lifted? See rxdebug.1 for example. */ + mdoc_nmsg(mdoc, n, MANDOCERR_NODOCBODY); + } + + return(erc ? 0 : 1); +} + +static int +post_st(POST_ARGS) +{ + struct mdoc_node *ch; + const char *p; + + if (NULL == (ch = mdoc->last->child)) { + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_MACROEMPTY); + mdoc_node_delete(mdoc, mdoc->last); + return(1); + } + + assert(MDOC_TEXT == ch->type); + + if (NULL == (p = mdoc_a2st(ch->string))) { + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADSTANDARD); + mdoc_node_delete(mdoc, mdoc->last); + } else { + free(ch->string); + ch->string = mandoc_strdup(p); + } + + return(1); +} + +static int +post_rs(POST_ARGS) +{ + struct mdoc_node *nn, *next, *prev; + int i, j; + + switch (mdoc->last->type) { + case (MDOC_HEAD): + check_count(mdoc, MDOC_HEAD, CHECK_WARN, CHECK_EQ, 0); + return(1); + case (MDOC_BODY): + if (mdoc->last->child) + break; + check_count(mdoc, MDOC_BODY, CHECK_WARN, CHECK_GT, 0); + return(1); + default: + return(1); + } + + /* + * Make sure only certain types of nodes are allowed within the + * the `Rs' body. Delete offending nodes and raise a warning. + * Do this before re-ordering for the sake of clarity. + */ + + next = NULL; + for (nn = mdoc->last->child; nn; nn = next) { + for (i = 0; i < RSORD_MAX; i++) + if (nn->tok == rsord[i]) + break; + + if (i < RSORD_MAX) { + if (MDOC__J == rsord[i] || MDOC__B == rsord[i]) + mdoc->last->norm->Rs.quote_T++; + next = nn->next; + continue; + } + + next = nn->next; + mdoc_nmsg(mdoc, nn, MANDOCERR_CHILD); + mdoc_node_delete(mdoc, nn); + } + + /* + * Nothing to sort if only invalid nodes were found + * inside the `Rs' body. + */ + + if (NULL == mdoc->last->child) + return(1); + + /* + * The full `Rs' block needs special handling to order the + * sub-elements according to `rsord'. Pick through each element + * and correctly order it. This is a insertion sort. + */ + + next = NULL; + for (nn = mdoc->last->child->next; nn; nn = next) { + /* Determine order of `nn'. */ + for (i = 0; i < RSORD_MAX; i++) + if (rsord[i] == nn->tok) + break; + + /* + * Remove `nn' from the chain. This somewhat + * repeats mdoc_node_unlink(), but since we're + * just re-ordering, there's no need for the + * full unlink process. + */ + + if (NULL != (next = nn->next)) + next->prev = nn->prev; + + if (NULL != (prev = nn->prev)) + prev->next = nn->next; + + nn->prev = nn->next = NULL; + + /* + * Scan back until we reach a node that's + * ordered before `nn'. + */ + + for ( ; prev ; prev = prev->prev) { + /* Determine order of `prev'. */ + for (j = 0; j < RSORD_MAX; j++) + if (rsord[j] == prev->tok) + break; + + if (j <= i) + break; + } + + /* + * Set `nn' back into its correct place in front + * of the `prev' node. + */ + + nn->prev = prev; + + if (prev) { + if (prev->next) + prev->next->prev = nn; + nn->next = prev->next; + prev->next = nn; + } else { + mdoc->last->child->prev = nn; + nn->next = mdoc->last->child; + mdoc->last->child = nn; + } + } + + return(1); +} + +static int +post_ns(POST_ARGS) +{ + + if (MDOC_LINE & mdoc->last->flags) + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_IGNNS); + return(1); +} + +static int +post_sh(POST_ARGS) +{ + + if (MDOC_HEAD == mdoc->last->type) + return(post_sh_head(mdoc)); + if (MDOC_BODY == mdoc->last->type) + return(post_sh_body(mdoc)); + + return(1); +} + +static int +post_sh_body(POST_ARGS) +{ + struct mdoc_node *n; + + if (SEC_NAME != mdoc->lastsec) + return(1); + + /* + * Warn if the NAME section doesn't contain the `Nm' and `Nd' + * macros (can have multiple `Nm' and one `Nd'). Note that the + * children of the BODY declaration can also be "text". + */ + + if (NULL == (n = mdoc->last->child)) { + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADNAMESEC); + return(1); + } + + for ( ; n && n->next; n = n->next) { + if (MDOC_ELEM == n->type && MDOC_Nm == n->tok) + continue; + if (MDOC_TEXT == n->type) + continue; + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADNAMESEC); + } + + assert(n); + if (MDOC_BLOCK == n->type && MDOC_Nd == n->tok) + return(1); + + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADNAMESEC); + return(1); +} + +static int +post_sh_head(POST_ARGS) +{ + char buf[BUFSIZ]; + struct mdoc_node *n; + enum mdoc_sec sec; + int c; + + /* + * Process a new section. Sections are either "named" or + * "custom". Custom sections are user-defined, while named ones + * follow a conventional order and may only appear in certain + * manual sections. + */ + + sec = SEC_CUSTOM; + buf[0] = '\0'; + if (-1 == (c = concat(buf, mdoc->last->child, BUFSIZ))) { + mdoc_nmsg(mdoc, mdoc->last->child, MANDOCERR_MEM); + return(0); + } else if (1 == c) + sec = a2sec(buf); + + /* The NAME should be first. */ + + if (SEC_NAME != sec && SEC_NONE == mdoc->lastnamed) + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NAMESECFIRST); + + /* The SYNOPSIS gets special attention in other areas. */ + + if (SEC_SYNOPSIS == sec) + mdoc->flags |= MDOC_SYNOPSIS; + else + mdoc->flags &= ~MDOC_SYNOPSIS; + + /* Mark our last section. */ + + mdoc->lastsec = sec; + + /* + * Set the section attribute for the current HEAD, for its + * parent BLOCK, and for the HEAD children; the latter can + * only be TEXT nodes, so no recursion is needed. + * For other blocks and elements, including .Sh BODY, this is + * done when allocating the node data structures, but for .Sh + * BLOCK and HEAD, the section is still unknown at that time. + */ + + mdoc->last->parent->sec = sec; + mdoc->last->sec = sec; + for (n = mdoc->last->child; n; n = n->next) + n->sec = sec; + + /* We don't care about custom sections after this. */ + + if (SEC_CUSTOM == sec) + return(1); + + /* + * Check whether our non-custom section is being repeated or is + * out of order. + */ + + if (sec == mdoc->lastnamed) + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_SECREP); + + if (sec < mdoc->lastnamed) + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_SECOOO); + + /* Mark the last named section. */ + + mdoc->lastnamed = sec; + + /* Check particular section/manual conventions. */ + + assert(mdoc->meta.msec); + + switch (sec) { + case (SEC_RETURN_VALUES): + /* FALLTHROUGH */ + case (SEC_ERRORS): + /* FALLTHROUGH */ + case (SEC_LIBRARY): + if (*mdoc->meta.msec == '2') + break; + if (*mdoc->meta.msec == '3') + break; + if (*mdoc->meta.msec == '9') + break; + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_SECMSEC); + break; + default: + break; + } + + return(1); +} + +static int +post_ignpar(POST_ARGS) +{ + struct mdoc_node *np; + + if (MDOC_BODY != mdoc->last->type) + return(1); + + if (NULL != (np = mdoc->last->child)) + if (MDOC_Pp == np->tok || MDOC_Lp == np->tok) { + mdoc_nmsg(mdoc, np, MANDOCERR_IGNPAR); + mdoc_node_delete(mdoc, np); + } + + if (NULL != (np = mdoc->last->last)) + if (MDOC_Pp == np->tok || MDOC_Lp == np->tok) { + mdoc_nmsg(mdoc, np, MANDOCERR_IGNPAR); + mdoc_node_delete(mdoc, np); + } + + return(1); +} + +static int +pre_par(PRE_ARGS) +{ + + if (NULL == mdoc->last) + return(1); + if (MDOC_ELEM != n->type && MDOC_BLOCK != n->type) + return(1); + + /* + * Don't allow prior `Lp' or `Pp' prior to a paragraph-type + * block: `Lp', `Pp', or non-compact `Bd' or `Bl'. + */ + + if (MDOC_Pp != mdoc->last->tok && MDOC_Lp != mdoc->last->tok) + return(1); + if (MDOC_Bl == n->tok && n->norm->Bl.comp) + return(1); + if (MDOC_Bd == n->tok && n->norm->Bd.comp) + return(1); + if (MDOC_It == n->tok && n->parent->norm->Bl.comp) + return(1); + + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_IGNPAR); + mdoc_node_delete(mdoc, mdoc->last); + return(1); +} + +static int +pre_literal(PRE_ARGS) +{ + + if (MDOC_BODY != n->type) + return(1); + + /* + * The `Dl' (note "el" not "one") and `Bd -literal' and `Bd + * -unfilled' macros set MDOC_LITERAL on entrance to the body. + */ + + switch (n->tok) { + case (MDOC_Dl): + mdoc->flags |= MDOC_LITERAL; + break; + case (MDOC_Bd): + if (DISP_literal == n->norm->Bd.type) + mdoc->flags |= MDOC_LITERAL; + if (DISP_unfilled == n->norm->Bd.type) + mdoc->flags |= MDOC_LITERAL; + break; + default: + abort(); + /* NOTREACHED */ + } + + return(1); +} + +static int +post_dd(POST_ARGS) +{ + char buf[DATESIZE]; + struct mdoc_node *n; + int c; + + if (mdoc->meta.date) + free(mdoc->meta.date); + + n = mdoc->last; + if (NULL == n->child || '\0' == n->child->string[0]) { + mdoc->meta.date = mandoc_normdate + (mdoc->parse, NULL, n->line, n->pos); + return(1); + } + + buf[0] = '\0'; + if (-1 == (c = concat(buf, n->child, DATESIZE))) { + mdoc_nmsg(mdoc, n->child, MANDOCERR_MEM); + return(0); + } + + assert(c); + mdoc->meta.date = mandoc_normdate + (mdoc->parse, buf, n->line, n->pos); + + return(1); +} + +static int +post_dt(POST_ARGS) +{ + struct mdoc_node *nn, *n; + const char *cp; + char *p; + + n = mdoc->last; + + if (mdoc->meta.title) + free(mdoc->meta.title); + if (mdoc->meta.vol) + free(mdoc->meta.vol); + if (mdoc->meta.arch) + free(mdoc->meta.arch); + + mdoc->meta.title = mdoc->meta.vol = mdoc->meta.arch = NULL; + + /* First make all characters uppercase. */ + + if (NULL != (nn = n->child)) + for (p = nn->string; *p; p++) { + if (toupper((unsigned char)*p) == *p) + continue; + + /* + * FIXME: don't be lazy: have this make all + * characters be uppercase and just warn once. + */ + mdoc_nmsg(mdoc, nn, MANDOCERR_UPPERCASE); + break; + } + + /* Handles: `.Dt' + * --> title = unknown, volume = local, msec = 0, arch = NULL + */ + + if (NULL == (nn = n->child)) { + /* XXX: make these macro values. */ + /* FIXME: warn about missing values. */ + mdoc->meta.title = mandoc_strdup("UNKNOWN"); + mdoc->meta.vol = mandoc_strdup("LOCAL"); + mdoc->meta.msec = mandoc_strdup("1"); + return(1); + } + + /* Handles: `.Dt TITLE' + * --> title = TITLE, volume = local, msec = 0, arch = NULL + */ + + mdoc->meta.title = mandoc_strdup + ('\0' == nn->string[0] ? "UNKNOWN" : nn->string); + + if (NULL == (nn = nn->next)) { + /* FIXME: warn about missing msec. */ + /* XXX: make this a macro value. */ + mdoc->meta.vol = mandoc_strdup("LOCAL"); + mdoc->meta.msec = mandoc_strdup("1"); + return(1); + } + + /* Handles: `.Dt TITLE SEC' + * --> title = TITLE, volume = SEC is msec ? + * format(msec) : SEC, + * msec = SEC is msec ? atoi(msec) : 0, + * arch = NULL + */ + + cp = mandoc_a2msec(nn->string); + if (cp) { + mdoc->meta.vol = mandoc_strdup(cp); + mdoc->meta.msec = mandoc_strdup(nn->string); + } else { + mdoc_nmsg(mdoc, n, MANDOCERR_BADMSEC); + mdoc->meta.vol = mandoc_strdup(nn->string); + mdoc->meta.msec = mandoc_strdup(nn->string); + } + + if (NULL == (nn = nn->next)) + return(1); + + /* Handles: `.Dt TITLE SEC VOL' + * --> title = TITLE, volume = VOL is vol ? + * format(VOL) : + * VOL is arch ? format(arch) : + * VOL + */ + + cp = mdoc_a2vol(nn->string); + if (cp) { + free(mdoc->meta.vol); + mdoc->meta.vol = mandoc_strdup(cp); + } else { + /* FIXME: warn about bad arch. */ + cp = mdoc_a2arch(nn->string); + if (NULL == cp) { + free(mdoc->meta.vol); + mdoc->meta.vol = mandoc_strdup(nn->string); + } else + mdoc->meta.arch = mandoc_strdup(cp); + } + + /* Ignore any subsequent parameters... */ + /* FIXME: warn about subsequent parameters. */ + + return(1); +} + +static int +post_prol(POST_ARGS) +{ + /* + * Remove prologue macros from the document after they're + * processed. The final document uses mdoc_meta for these + * values and discards the originals. + */ + + mdoc_node_delete(mdoc, mdoc->last); + if (mdoc->meta.title && mdoc->meta.date && mdoc->meta.os) + mdoc->flags |= MDOC_PBODY; + + return(1); +} + +static int +post_bx(POST_ARGS) +{ + struct mdoc_node *n; + + /* + * Make `Bx's second argument always start with an uppercase + * letter. Groff checks if it's an "accepted" term, but we just + * uppercase blindly. + */ + + n = mdoc->last->child; + if (n && NULL != (n = n->next)) + *n->string = (char)toupper + ((unsigned char)*n->string); + + return(1); +} + +static int +post_os(POST_ARGS) +{ + struct mdoc_node *n; + char buf[BUFSIZ]; + int c; +#ifndef OSNAME + struct utsname utsname; +#endif + + n = mdoc->last; + + /* + * Set the operating system by way of the `Os' macro. Note that + * if an argument isn't provided and -DOSNAME="\"foo\"" is + * provided during compilation, this value will be used instead + * of filling in "sysname release" from uname(). + */ + + if (mdoc->meta.os) + free(mdoc->meta.os); + + buf[0] = '\0'; + if (-1 == (c = concat(buf, n->child, BUFSIZ))) { + mdoc_nmsg(mdoc, n->child, MANDOCERR_MEM); + return(0); + } + + assert(c); + + /* XXX: yes, these can all be dynamically-adjusted buffers, but + * it's really not worth the extra hackery. + */ + + if ('\0' == buf[0]) { +#ifdef OSNAME + if (strlcat(buf, OSNAME, BUFSIZ) >= BUFSIZ) { + mdoc_nmsg(mdoc, n, MANDOCERR_MEM); + return(0); + } +#else /*!OSNAME */ + if (-1 == uname(&utsname)) { + mdoc_nmsg(mdoc, n, MANDOCERR_UNAME); + mdoc->meta.os = mandoc_strdup("UNKNOWN"); + return(post_prol(mdoc)); + } + + if (strlcat(buf, utsname.sysname, BUFSIZ) >= BUFSIZ) { + mdoc_nmsg(mdoc, n, MANDOCERR_MEM); + return(0); + } + if (strlcat(buf, " ", BUFSIZ) >= BUFSIZ) { + mdoc_nmsg(mdoc, n, MANDOCERR_MEM); + return(0); + } + if (strlcat(buf, utsname.release, BUFSIZ) >= BUFSIZ) { + mdoc_nmsg(mdoc, n, MANDOCERR_MEM); + return(0); + } +#endif /*!OSNAME*/ + } + + mdoc->meta.os = mandoc_strdup(buf); + return(1); +} + +static int +post_std(POST_ARGS) +{ + struct mdoc_node *nn, *n; + + n = mdoc->last; + + /* + * Macros accepting `-std' as an argument have the name of the + * current document (`Nm') filled in as the argument if it's not + * provided. + */ + + if (n->child) + return(1); + + if (NULL == mdoc->meta.name) + return(1); + + nn = n; + mdoc->next = MDOC_NEXT_CHILD; + + if ( ! mdoc_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name)) + return(0); + + mdoc->last = nn; + return(1); +} + +/* + * Concatenate a node, stopping at the first non-text. + * Concatenation is separated by a single whitespace. + * Returns -1 on fatal (string overrun) error, 0 if child nodes were + * encountered, 1 otherwise. + */ +static int +concat(char *p, const struct mdoc_node *n, size_t sz) +{ + + for ( ; NULL != n; n = n->next) { + if (MDOC_TEXT != n->type) + return(0); + if ('\0' != p[0] && strlcat(p, " ", sz) >= sz) + return(-1); + if (strlcat(p, n->string, sz) >= sz) + return(-1); + concat(p, n->child, sz); + } + + return(1); +} + +static enum mdoc_sec +a2sec(const char *p) +{ + int i; + + for (i = 0; i < (int)SEC__MAX; i++) + if (secnames[i] && 0 == strcmp(p, secnames[i])) + return((enum mdoc_sec)i); + + return(SEC_CUSTOM); +} + +static size_t +macro2len(enum mdoct macro) +{ + + switch (macro) { + case(MDOC_Ad): + return(12); + case(MDOC_Ao): + return(12); + case(MDOC_An): + return(12); + case(MDOC_Aq): + return(12); + case(MDOC_Ar): + return(12); + case(MDOC_Bo): + return(12); + case(MDOC_Bq): + return(12); + case(MDOC_Cd): + return(12); + case(MDOC_Cm): + return(10); + case(MDOC_Do): + return(10); + case(MDOC_Dq): + return(12); + case(MDOC_Dv): + return(12); + case(MDOC_Eo): + return(12); + case(MDOC_Em): + return(10); + case(MDOC_Er): + return(17); + case(MDOC_Ev): + return(15); + case(MDOC_Fa): + return(12); + case(MDOC_Fl): + return(10); + case(MDOC_Fo): + return(16); + case(MDOC_Fn): + return(16); + case(MDOC_Ic): + return(10); + case(MDOC_Li): + return(16); + case(MDOC_Ms): + return(6); + case(MDOC_Nm): + return(10); + case(MDOC_No): + return(12); + case(MDOC_Oo): + return(10); + case(MDOC_Op): + return(14); + case(MDOC_Pa): + return(32); + case(MDOC_Pf): + return(12); + case(MDOC_Po): + return(12); + case(MDOC_Pq): + return(12); + case(MDOC_Ql): + return(16); + case(MDOC_Qo): + return(12); + case(MDOC_So): + return(12); + case(MDOC_Sq): + return(12); + case(MDOC_Sy): + return(6); + case(MDOC_Sx): + return(16); + case(MDOC_Tn): + return(10); + case(MDOC_Va): + return(12); + case(MDOC_Vt): + return(12); + case(MDOC_Xr): + return(10); + default: + break; + }; + return(0); +} diff --git a/msec.c b/msec.c new file mode 100644 index 000000000000..dd7d11c650fa --- /dev/null +++ b/msec.c @@ -0,0 +1,37 @@ +/* $Id: msec.c,v 1.10 2011/12/02 01:37:14 schwarze Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdlib.h> +#include <string.h> + +#include "mandoc.h" +#include "libmandoc.h" + +#define LINE(x, y) \ + if (0 == strcmp(p, x)) return(y); + +const char * +mandoc_a2msec(const char *p) +{ + +#include "msec.in" + + return(NULL); +} diff --git a/msec.in b/msec.in new file mode 100644 index 000000000000..f3aebb46a109 --- /dev/null +++ b/msec.in @@ -0,0 +1,40 @@ +/* $Id: msec.in,v 1.6 2010/06/19 20:46:28 kristaps Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * These are all possible manual-section macros and what they correspond + * to when rendered as the volume title. + * + * Be sure to escape strings. + */ + +LINE("1", "General Commands Manual") +LINE("2", "System Calls Manual") +LINE("3", "Library Functions Manual") +LINE("3p", "Perl Library Functions Manual") +LINE("4", "Kernel Interfaces Manual") +LINE("5", "File Formats Manual") +LINE("6", "Games Manual") +LINE("7", "Miscellaneous Information Manual") +LINE("8", "System Manager\'s Manual") +LINE("9", "Kernel Developer\'s Manual") +LINE("X11", "X11 Developer\'s Manual") +LINE("X11R6", "X11 Developer\'s Manual") +LINE("unass", "Unassociated") +LINE("local", "Local") +LINE("draft", "Draft") +LINE("paper", "Paper") diff --git a/out.c b/out.c new file mode 100644 index 000000000000..8dbd68ac11ad --- /dev/null +++ b/out.c @@ -0,0 +1,303 @@ +/* $Id: out.c,v 1.43 2011/09/20 23:05:49 schwarze Exp $ */ +/* + * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/types.h> + +#include <assert.h> +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "mandoc.h" +#include "out.h" + +static void tblcalc_data(struct rofftbl *, struct roffcol *, + const struct tbl *, const struct tbl_dat *); +static void tblcalc_literal(struct rofftbl *, struct roffcol *, + const struct tbl_dat *); +static void tblcalc_number(struct rofftbl *, struct roffcol *, + const struct tbl *, const struct tbl_dat *); + +/* + * Convert a `scaling unit' to a consistent form, or fail. Scaling + * units are documented in groff.7, mdoc.7, man.7. + */ +int +a2roffsu(const char *src, struct roffsu *dst, enum roffscale def) +{ + char buf[BUFSIZ], hasd; + int i; + enum roffscale unit; + + if ('\0' == *src) + return(0); + + i = hasd = 0; + + switch (*src) { + case ('+'): + src++; + break; + case ('-'): + buf[i++] = *src++; + break; + default: + break; + } + + if ('\0' == *src) + return(0); + + while (i < BUFSIZ) { + if ( ! isdigit((unsigned char)*src)) { + if ('.' != *src) + break; + else if (hasd) + break; + else + hasd = 1; + } + buf[i++] = *src++; + } + + if (BUFSIZ == i || (*src && *(src + 1))) + return(0); + + buf[i] = '\0'; + + switch (*src) { + case ('c'): + unit = SCALE_CM; + break; + case ('i'): + unit = SCALE_IN; + break; + case ('P'): + unit = SCALE_PC; + break; + case ('p'): + unit = SCALE_PT; + break; + case ('f'): + unit = SCALE_FS; + break; + case ('v'): + unit = SCALE_VS; + break; + case ('m'): + unit = SCALE_EM; + break; + case ('\0'): + if (SCALE_MAX == def) + return(0); + unit = SCALE_BU; + break; + case ('u'): + unit = SCALE_BU; + break; + case ('M'): + unit = SCALE_MM; + break; + case ('n'): + unit = SCALE_EN; + break; + default: + return(0); + } + + /* FIXME: do this in the caller. */ + if ((dst->scale = atof(buf)) < 0) + dst->scale = 0; + dst->unit = unit; + return(1); +} + +/* + * Calculate the abstract widths and decimal positions of columns in a + * table. This routine allocates the columns structures then runs over + * all rows and cells in the table. The function pointers in "tbl" are + * used for the actual width calculations. + */ +void +tblcalc(struct rofftbl *tbl, const struct tbl_span *sp) +{ + const struct tbl_dat *dp; + const struct tbl_head *hp; + struct roffcol *col; + int spans; + + /* + * Allocate the master column specifiers. These will hold the + * widths and decimal positions for all cells in the column. It + * must be freed and nullified by the caller. + */ + + assert(NULL == tbl->cols); + tbl->cols = mandoc_calloc + ((size_t)sp->tbl->cols, sizeof(struct roffcol)); + + hp = sp->head; + + for ( ; sp; sp = sp->next) { + if (TBL_SPAN_DATA != sp->pos) + continue; + spans = 1; + /* + * Account for the data cells in the layout, matching it + * to data cells in the data section. + */ + for (dp = sp->first; dp; dp = dp->next) { + /* Do not used spanned cells in the calculation. */ + if (0 < --spans) + continue; + spans = dp->spans; + if (1 < spans) + continue; + assert(dp->layout); + col = &tbl->cols[dp->layout->head->ident]; + tblcalc_data(tbl, col, sp->tbl, dp); + } + } + + /* + * Calculate width of the spanners. These get one space for a + * vertical line, two for a double-vertical line. + */ + + for ( ; hp; hp = hp->next) { + col = &tbl->cols[hp->ident]; + switch (hp->pos) { + case (TBL_HEAD_VERT): + col->width = (*tbl->len)(1, tbl->arg); + break; + case (TBL_HEAD_DVERT): + col->width = (*tbl->len)(2, tbl->arg); + break; + default: + break; + } + } +} + +static void +tblcalc_data(struct rofftbl *tbl, struct roffcol *col, + const struct tbl *tp, const struct tbl_dat *dp) +{ + size_t sz; + + /* Branch down into data sub-types. */ + + switch (dp->layout->pos) { + case (TBL_CELL_HORIZ): + /* FALLTHROUGH */ + case (TBL_CELL_DHORIZ): + sz = (*tbl->len)(1, tbl->arg); + if (col->width < sz) + col->width = sz; + break; + case (TBL_CELL_LONG): + /* FALLTHROUGH */ + case (TBL_CELL_CENTRE): + /* FALLTHROUGH */ + case (TBL_CELL_LEFT): + /* FALLTHROUGH */ + case (TBL_CELL_RIGHT): + tblcalc_literal(tbl, col, dp); + break; + case (TBL_CELL_NUMBER): + tblcalc_number(tbl, col, tp, dp); + break; + case (TBL_CELL_DOWN): + break; + default: + abort(); + /* NOTREACHED */ + } +} + +static void +tblcalc_literal(struct rofftbl *tbl, struct roffcol *col, + const struct tbl_dat *dp) +{ + size_t sz; + const char *str; + + str = dp->string ? dp->string : ""; + sz = (*tbl->slen)(str, tbl->arg); + + if (col->width < sz) + col->width = sz; +} + +static void +tblcalc_number(struct rofftbl *tbl, struct roffcol *col, + const struct tbl *tp, const struct tbl_dat *dp) +{ + int i; + size_t sz, psz, ssz, d; + const char *str; + char *cp; + char buf[2]; + + /* + * First calculate number width and decimal place (last + 1 for + * non-decimal numbers). If the stored decimal is subsequent to + * ours, make our size longer by that difference + * (right-"shifting"); similarly, if ours is subsequent the + * stored, then extend the stored size by the difference. + * Finally, re-assign the stored values. + */ + + str = dp->string ? dp->string : ""; + sz = (*tbl->slen)(str, tbl->arg); + + /* FIXME: TBL_DATA_HORIZ et al.? */ + + buf[0] = tp->decimal; + buf[1] = '\0'; + + psz = (*tbl->slen)(buf, tbl->arg); + + if (NULL != (cp = strrchr(str, tp->decimal))) { + buf[1] = '\0'; + for (ssz = 0, i = 0; cp != &str[i]; i++) { + buf[0] = str[i]; + ssz += (*tbl->slen)(buf, tbl->arg); + } + d = ssz + psz; + } else + d = sz + psz; + + /* Adjust the settings for this column. */ + + if (col->decimal > d) { + sz += col->decimal - d; + d = col->decimal; + } else + col->width += d - col->decimal; + + if (sz > col->width) + col->width = sz; + if (d > col->decimal) + col->decimal = d; +} diff --git a/out.h b/out.h new file mode 100644 index 000000000000..1c18c6c3145a --- /dev/null +++ b/out.h @@ -0,0 +1,71 @@ +/* $Id: out.h,v 1.21 2011/07/17 15:24:25 kristaps Exp $ */ +/* + * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef OUT_H +#define OUT_H + +enum roffscale { + SCALE_CM, /* centimeters (c) */ + SCALE_IN, /* inches (i) */ + SCALE_PC, /* pica (P) */ + SCALE_PT, /* points (p) */ + SCALE_EM, /* ems (m) */ + SCALE_MM, /* mini-ems (M) */ + SCALE_EN, /* ens (n) */ + SCALE_BU, /* default horizontal (u) */ + SCALE_VS, /* default vertical (v) */ + SCALE_FS, /* syn. for u (f) */ + SCALE_MAX +}; + +struct roffcol { + size_t width; /* width of cell */ + size_t decimal; /* decimal position in cell */ +}; + +struct roffsu { + enum roffscale unit; + double scale; +}; + +typedef size_t (*tbl_strlen)(const char *, void *); +typedef size_t (*tbl_len)(size_t, void *); + +struct rofftbl { + tbl_strlen slen; /* calculate string length */ + tbl_len len; /* produce width of empty space */ + struct roffcol *cols; /* master column specifiers */ + void *arg; /* passed to slen and len */ +}; + +__BEGIN_DECLS + +#define SCALE_VS_INIT(p, v) \ + do { (p)->unit = SCALE_VS; \ + (p)->scale = (v); } \ + while (/* CONSTCOND */ 0) + +#define SCALE_HS_INIT(p, v) \ + do { (p)->unit = SCALE_BU; \ + (p)->scale = (v); } \ + while (/* CONSTCOND */ 0) + +int a2roffsu(const char *, struct roffsu *, enum roffscale); +void tblcalc(struct rofftbl *tbl, const struct tbl_span *); + +__END_DECLS + +#endif /*!OUT_H*/ diff --git a/preconv.1 b/preconv.1 new file mode 100644 index 000000000000..c4cb8c5738fd --- /dev/null +++ b/preconv.1 @@ -0,0 +1,158 @@ +.\" $Id: preconv.1,v 1.6 2011/12/25 19:35:44 kristaps Exp $ +.\" +.\" Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: December 25 2011 $ +.Dt PRECONV 1 +.Os +.Sh NAME +.Nm preconv +.Nd recode multibyte UNIX manuals +.Sh SYNOPSIS +.Nm preconv +.Op Fl D Ar enc +.Op Fl e Ar enc +.Op Ar file +.Sh DESCRIPTION +The +.Nm +utility recodes multibyte +.Ux +manual files into +.Xr mandoc 1 +.Po +or other troff system supporting the +.Sq \e[uNNNN] +escape sequence +.Pc +input. +.Pp +By default, it parses from standard output, determining encoding as +described in +.Sx Algorithm . +.Pp +Its arguments are as follows: +.Bl -tag -width Ds +.It Fl D Ar enc +The default encoding. +.It Fl e Ar enc +The document's encoding. +.It Ar file +The input file. +.El +.Pp +The recoded input is written to standard output: Unicode characters in +the ASCII range are printed as regular ASCII characters, while those +above this range are printed using the +.Sq \e[uNNNN] +format documented in +.Xr mandoc_char 7 . +.Pp +If input bytes are improperly formed in the current encoding, they're +passed unmodified to standard output. +For some encodings, such as UTF-8, unrecoverable input sequences will +cause +.Nm +to stop processing and exit. +.Ss Algorithm +An encoding is chosen according to the following steps: +.Bl -enum +.It +From the argument passed to +.Fl e Ar enc . +.It +If a BOM exists, UTF\-8 encoding is selected. +.It +From the coding tags parsed from +.Qq File Variables +on the first two lines of input. +A file variable is an input line of the form +.Pp +.Dl \%.\e\(dq -*- key: val [; key: val ]* -*- +.Pp +A coding tag variable is where +.Cm key +is +.Qq coding +and +.Cm val +is the name of the encoding. +A typical file variable with a coding tag is +.Pp +.Dl \%.\e\(dq -*- mode: troff; coding: utf-8 -*- +.It +From the argument passed to +.Fl D Ar enc . +.It +If all else fails, Latin\-1 is used. +.El +.Pp +The +.Nm +utility recognises the UTF\-8, us\-ascii, and latin\-1 encodings as +passed to the +.Fl e +and +.Fl D +arguments, or as coding tags. +Encodings are matched case-insensitively. +.\" .Sh IMPLEMENTATION NOTES +.\" Not used in OpenBSD. +.\" .Sh RETURN VALUES +.\" For sections 2, 3, & 9 only. +.\" .Sh ENVIRONMENT +.\" For sections 1, 6, 7, & 8 only. +.\" .Sh FILES +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +Explicitly page a UTF\-8 manual +.Pa foo.1 +in the current locale: +.Pp +.Dl $ preconv \-e utf\-8 foo.1 | mandoc -Tlocale | less +.\" .Sh DIAGNOSTICS +.\" For sections 1, 4, 6, 7, & 8 only. +.\" .Sh ERRORS +.\" For sections 2, 3, & 9 only. +.Sh SEE ALSO +.Xr mandoc 1 , +.Xr mandoc_char 7 +.Sh STANDARDS +The +.Nm +utility references the US-ASCII character set standard, ANSI_X3.4\-1968; +the Latin\-1 character set standard, ISO/IEC 8859\-1:1998; the UTF\-8 +character set standard; and UCS (Unicode), ISO/IEC 10646. +.Sh HISTORY +The +.Nm +utility first appeared in the GNU troff +.Pq Dq groff +system in December 2005, authored by Tomohiro Kubota and Werner +Lemberg. +The implementation that is part of the +.Xr mandoc 1 +utility appeared in May 2011. +.Sh AUTHORS +The +.Nm +utility was written by +.An Kristaps Dzonsons , +.Mt kristaps@bsd.lv . +.\" .Sh CAVEATS +.\" .Sh BUGS +.\" .Sh SECURITY CONSIDERATIONS +.\" Not used in OpenBSD. diff --git a/preconv.c b/preconv.c new file mode 100644 index 000000000000..a0b2d6415e99 --- /dev/null +++ b/preconv.c @@ -0,0 +1,528 @@ +/* $Id: preconv.c,v 1.5 2011/07/24 18:15:14 kristaps Exp $ */ +/* + * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_MMAP +#include <sys/stat.h> +#include <sys/mman.h> +#endif + +#include <assert.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +/* + * The read_whole_file() and resize_buf() functions are copied from + * read.c, including all dependency code (MAP_FILE, etc.). + */ + +#ifndef MAP_FILE +#define MAP_FILE 0 +#endif + +enum enc { + ENC_UTF_8, /* UTF-8 */ + ENC_US_ASCII, /* US-ASCII */ + ENC_LATIN_1, /* Latin-1 */ + ENC__MAX +}; + +struct buf { + char *buf; /* binary input buffer */ + size_t sz; /* size of binary buffer */ + size_t offs; /* starting buffer offset */ +}; + +struct encode { + const char *name; + int (*conv)(const struct buf *); +}; + +static int cue_enc(const struct buf *, size_t *, enum enc *); +static int conv_latin_1(const struct buf *); +static int conv_us_ascii(const struct buf *); +static int conv_utf_8(const struct buf *); +static int read_whole_file(const char *, int, + struct buf *, int *); +static void resize_buf(struct buf *, size_t); +static void usage(void); + +static const struct encode encs[ENC__MAX] = { + { "utf-8", conv_utf_8 }, /* ENC_UTF_8 */ + { "us-ascii", conv_us_ascii }, /* ENC_US_ASCII */ + { "latin-1", conv_latin_1 }, /* ENC_LATIN_1 */ +}; + +static const char *progname; + +static void +usage(void) +{ + + fprintf(stderr, "usage: %s " + "[-D enc] " + "[-e ENC] " + "[file]\n", progname); +} + +static int +conv_latin_1(const struct buf *b) +{ + size_t i; + unsigned char cu; + const char *cp; + + cp = b->buf + (int)b->offs; + + /* + * Latin-1 falls into the first 256 code-points of Unicode, so + * there's no need for any sort of translation. Just make the + * 8-bit characters use the Unicode escape. + * Note that binary values 128 < v < 160 are passed through + * unmodified to mandoc. + */ + + for (i = b->offs; i < b->sz; i++) { + cu = (unsigned char)*cp++; + cu < 160U ? putchar(cu) : printf("\\[u%.4X]", cu); + } + + return(1); +} + +static int +conv_us_ascii(const struct buf *b) +{ + + /* + * US-ASCII has no conversion since it falls into the first 128 + * bytes of Unicode. + */ + + fwrite(b->buf, 1, b->sz, stdout); + return(1); +} + +static int +conv_utf_8(const struct buf *b) +{ + int state, be; + unsigned int accum; + size_t i; + unsigned char cu; + const char *cp; + const long one = 1L; + + cp = b->buf + (int)b->offs; + state = 0; + accum = 0U; + be = 0; + + /* Quick test for big-endian value. */ + + if ( ! (*((const char *)(&one)))) + be = 1; + + for (i = b->offs; i < b->sz; i++) { + cu = (unsigned char)*cp++; + if (state) { + if ( ! (cu & 128) || (cu & 64)) { + /* Bad sequence header. */ + return(0); + } + + /* Accept only legitimate bit patterns. */ + + if (cu > 191 || cu < 128) { + /* Bad in-sequence bits. */ + return(0); + } + + accum |= (cu & 63) << --state * 6; + + /* + * Accum is held in little-endian order as + * stipulated by the UTF-8 sequence coding. We + * need to convert to a native big-endian if our + * architecture requires it. + */ + + if (0 == state && be) + accum = (accum >> 24) | + ((accum << 8) & 0x00FF0000) | + ((accum >> 8) & 0x0000FF00) | + (accum << 24); + + if (0 == state) { + accum < 128U ? putchar(accum) : + printf("\\[u%.4X]", accum); + accum = 0U; + } + } else if (cu & (1 << 7)) { + /* + * Entering a UTF-8 state: if we encounter a + * UTF-8 bitmask, calculate the expected UTF-8 + * state from it. + */ + for (state = 0; state < 7; state++) + if ( ! (cu & (1 << (7 - state)))) + break; + + /* Accept only legitimate bit patterns. */ + + switch (state) { + case (4): + if (cu <= 244 && cu >= 240) { + accum = (cu & 7) << 18; + break; + } + /* Bad 4-sequence start bits. */ + return(0); + case (3): + if (cu <= 239 && cu >= 224) { + accum = (cu & 15) << 12; + break; + } + /* Bad 3-sequence start bits. */ + return(0); + case (2): + if (cu <= 223 && cu >= 194) { + accum = (cu & 31) << 6; + break; + } + /* Bad 2-sequence start bits. */ + return(0); + default: + /* Bad sequence bit mask. */ + return(0); + } + state--; + } else + putchar(cu); + } + + if (0 != state) { + /* Bad trailing bits. */ + return(0); + } + + return(1); +} + +static void +resize_buf(struct buf *buf, size_t initial) +{ + + buf->sz = buf->sz > initial / 2 ? + 2 * buf->sz : initial; + + buf->buf = realloc(buf->buf, buf->sz); + if (NULL == buf->buf) { + perror(NULL); + exit(EXIT_FAILURE); + } +} + +static int +read_whole_file(const char *f, int fd, + struct buf *fb, int *with_mmap) +{ + size_t off; + ssize_t ssz; + +#ifdef HAVE_MMAP + struct stat st; + if (-1 == fstat(fd, &st)) { + perror(f); + return(0); + } + + /* + * If we're a regular file, try just reading in the whole entry + * via mmap(). This is faster than reading it into blocks, and + * since each file is only a few bytes to begin with, I'm not + * concerned that this is going to tank any machines. + */ + + if (S_ISREG(st.st_mode) && st.st_size >= (1U << 31)) { + fprintf(stderr, "%s: input too large\n", f); + return(0); + } + + if (S_ISREG(st.st_mode)) { + *with_mmap = 1; + fb->sz = (size_t)st.st_size; + fb->buf = mmap(NULL, fb->sz, PROT_READ, + MAP_FILE|MAP_SHARED, fd, 0); + if (fb->buf != MAP_FAILED) + return(1); + } +#endif + + /* + * If this isn't a regular file (like, say, stdin), then we must + * go the old way and just read things in bit by bit. + */ + + *with_mmap = 0; + off = 0; + fb->sz = 0; + fb->buf = NULL; + for (;;) { + if (off == fb->sz && fb->sz == (1U << 31)) { + fprintf(stderr, "%s: input too large\n", f); + break; + } + + if (off == fb->sz) + resize_buf(fb, 65536); + + ssz = read(fd, fb->buf + (int)off, fb->sz - off); + if (ssz == 0) { + fb->sz = off; + return(1); + } + if (ssz == -1) { + perror(f); + break; + } + off += (size_t)ssz; + } + + free(fb->buf); + fb->buf = NULL; + return(0); +} + +static int +cue_enc(const struct buf *b, size_t *offs, enum enc *enc) +{ + const char *ln, *eoln, *eoph; + size_t sz, phsz, nsz; + int i; + + ln = b->buf + (int)*offs; + sz = b->sz - *offs; + + /* Look for the end-of-line. */ + + if (NULL == (eoln = memchr(ln, '\n', sz))) + return(-1); + + /* Set next-line marker. */ + + *offs = (size_t)((eoln + 1) - b->buf); + + /* Check if we have the correct header/trailer. */ + + if ((sz = (size_t)(eoln - ln)) < 10 || + memcmp(ln, ".\\\" -*-", 7) || + memcmp(eoln - 3, "-*-", 3)) + return(0); + + /* Move after the header and adjust for the trailer. */ + + ln += 7; + sz -= 10; + + while (sz > 0) { + while (sz > 0 && ' ' == *ln) { + ln++; + sz--; + } + if (0 == sz) + break; + + /* Find the end-of-phrase marker (or eoln). */ + + if (NULL == (eoph = memchr(ln, ';', sz))) + eoph = eoln - 3; + else + eoph++; + + /* Only account for the "coding" phrase. */ + + if ((phsz = (size_t)(eoph - ln)) < 7 || + strncasecmp(ln, "coding:", 7)) { + sz -= phsz; + ln += phsz; + continue; + } + + sz -= 7; + ln += 7; + + while (sz > 0 && ' ' == *ln) { + ln++; + sz--; + } + if (0 == sz) + break; + + /* Check us against known encodings. */ + + for (i = 0; i < (int)ENC__MAX; i++) { + nsz = strlen(encs[i].name); + if (phsz < nsz) + continue; + if (strncasecmp(ln, encs[i].name, nsz)) + continue; + + *enc = (enum enc)i; + return(1); + } + + /* Unknown encoding. */ + + *enc = ENC__MAX; + return(1); + } + + return(0); +} + +int +main(int argc, char *argv[]) +{ + int i, ch, map, fd, rc; + struct buf b; + const char *fn; + enum enc enc, def; + unsigned char bom[3] = { 0xEF, 0xBB, 0xBF }; + size_t offs; + extern int optind; + extern char *optarg; + + progname = strrchr(argv[0], '/'); + if (progname == NULL) + progname = argv[0]; + else + ++progname; + + fn = "<stdin>"; + fd = STDIN_FILENO; + rc = EXIT_FAILURE; + enc = def = ENC__MAX; + map = 0; + + memset(&b, 0, sizeof(struct buf)); + + while (-1 != (ch = getopt(argc, argv, "D:e:rdvh"))) + switch (ch) { + case ('D'): + /* FALLTHROUGH */ + case ('e'): + for (i = 0; i < (int)ENC__MAX; i++) { + if (strcasecmp(optarg, encs[i].name)) + continue; + break; + } + if (i < (int)ENC__MAX) { + if ('D' == ch) + def = (enum enc)i; + else + enc = (enum enc)i; + break; + } + + fprintf(stderr, "%s: Bad encoding\n", optarg); + return(EXIT_FAILURE); + case ('r'): + /* FALLTHROUGH */ + case ('d'): + /* FALLTHROUGH */ + case ('v'): + /* Compatibility with GNU preconv. */ + break; + case ('h'): + /* Compatibility with GNU preconv. */ + /* FALLTHROUGH */ + default: + usage(); + return(EXIT_FAILURE); + } + + argc -= optind; + argv += optind; + + /* + * Open and read the first argument on the command-line. + * If we don't have one, we default to stdin. + */ + + if (argc > 0) { + fn = *argv; + fd = open(fn, O_RDONLY, 0); + if (-1 == fd) { + perror(fn); + return(EXIT_FAILURE); + } + } + + if ( ! read_whole_file(fn, fd, &b, &map)) + goto out; + + /* Try to read the UTF-8 BOM. */ + + if (ENC__MAX == enc) + if (b.sz > 3 && 0 == memcmp(b.buf, bom, 3)) { + b.offs = 3; + enc = ENC_UTF_8; + } + + /* Try reading from the "-*-" cue. */ + + if (ENC__MAX == enc) { + offs = b.offs; + ch = cue_enc(&b, &offs, &enc); + if (0 == ch) + ch = cue_enc(&b, &offs, &enc); + } + + /* + * No encoding has been detected. + * Thus, we either fall into our default encoder, if specified, + * or use Latin-1 if all else fails. + */ + + if (ENC__MAX == enc) + enc = ENC__MAX == def ? ENC_LATIN_1 : def; + + if ( ! (*encs[(int)enc].conv)(&b)) { + fprintf(stderr, "%s: Bad encoding\n", fn); + goto out; + } + + rc = EXIT_SUCCESS; +out: +#ifdef HAVE_MMAP + if (map) + munmap(b.buf, b.sz); + else +#endif + free(b.buf); + + if (fd > STDIN_FILENO) + close(fd); + + return(rc); +} diff --git a/predefs.in b/predefs.in new file mode 100644 index 000000000000..70074bb617b5 --- /dev/null +++ b/predefs.in @@ -0,0 +1,65 @@ +/* $Id: predefs.in,v 1.3 2011/07/31 11:36:49 schwarze Exp $ */ +/* + * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * The predefined-string translation tables. Each corresponds to a + * predefined strings from (e.g.) tmac/mdoc/doc-nroff. The left-hand + * side corresponds to the input sequence (\*x, \*(xx and so on). The + * right-hand side is what's produced by libroff. + * + * XXX - C-escape strings! + * XXX - update PREDEF_MAX in roff.c if adding more! + */ + +PREDEF("Am", "&") +PREDEF("Ba", "|") +PREDEF("Ge", "\\(>=") +PREDEF("Gt", ">") +PREDEF("If", "infinity") +PREDEF("Le", "\\(<=") +PREDEF("Lq", "\\(lq") +PREDEF("Lt", "<") +PREDEF("Na", "NaN") +PREDEF("Ne", "\\(!=") +PREDEF("Pi", "pi") +PREDEF("Pm", "\\(+-") +PREDEF("Rq", "\\(rq") +PREDEF("left-bracket", "[") +PREDEF("left-parenthesis", "(") +PREDEF("lp", "(") +PREDEF("left-singlequote", "\\(oq") +PREDEF("q", "\\(dq") +PREDEF("quote-left", "\\(oq") +PREDEF("quote-right", "\\(cq") +PREDEF("R", "\\(rg") +PREDEF("right-bracket", "]") +PREDEF("right-parenthesis", ")") +PREDEF("rp", ")") +PREDEF("right-singlequote", "\\(cq") +PREDEF("Tm", "(Tm)") +PREDEF("Px", "POSIX") +PREDEF("Ai", "ANSI") +PREDEF("\'", "\\\'") +PREDEF("aa", "\\(aa") +PREDEF("ga", "\\(ga") +PREDEF("`", "\\`") +PREDEF("lq", "\\(lq") +PREDEF("rq", "\\(rq") +PREDEF("ua", "\\(ua") +PREDEF("va", "\\(va") +PREDEF("<=", "\\(<=") +PREDEF(">=", "\\(>=") diff --git a/read.c b/read.c new file mode 100644 index 000000000000..5b14e357d2bd --- /dev/null +++ b/read.c @@ -0,0 +1,846 @@ +/* $Id: read.c,v 1.28 2012/02/16 20:51:31 joerg Exp $ */ +/* + * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * Copyright (c) 2010, 2011 Ingo Schwarze <schwarze@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_MMAP +# include <sys/stat.h> +# include <sys/mman.h> +#endif + +#include <assert.h> +#include <ctype.h> +#include <fcntl.h> +#include <stdarg.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "mandoc.h" +#include "libmandoc.h" +#include "mdoc.h" +#include "man.h" +#include "main.h" + +#ifndef MAP_FILE +#define MAP_FILE 0 +#endif + +#define REPARSE_LIMIT 1000 + +struct buf { + char *buf; /* binary input buffer */ + size_t sz; /* size of binary buffer */ +}; + +struct mparse { + enum mandoclevel file_status; /* status of current parse */ + enum mandoclevel wlevel; /* ignore messages below this */ + int line; /* line number in the file */ + enum mparset inttype; /* which parser to use */ + struct man *pman; /* persistent man parser */ + struct mdoc *pmdoc; /* persistent mdoc parser */ + struct man *man; /* man parser */ + struct mdoc *mdoc; /* mdoc parser */ + struct roff *roff; /* roff parser (!NULL) */ + int reparse_count; /* finite interp. stack */ + mandocmsg mmsg; /* warning/error message handler */ + void *arg; /* argument to mmsg */ + const char *file; + struct buf *secondary; +}; + +static void resize_buf(struct buf *, size_t); +static void mparse_buf_r(struct mparse *, struct buf, int); +static void mparse_readfd_r(struct mparse *, int, const char *, int); +static void pset(const char *, int, struct mparse *); +static int read_whole_file(const char *, int, struct buf *, int *); +static void mparse_end(struct mparse *); + +static const enum mandocerr mandoclimits[MANDOCLEVEL_MAX] = { + MANDOCERR_OK, + MANDOCERR_WARNING, + MANDOCERR_WARNING, + MANDOCERR_ERROR, + MANDOCERR_FATAL, + MANDOCERR_MAX, + MANDOCERR_MAX +}; + +static const char * const mandocerrs[MANDOCERR_MAX] = { + "ok", + + "generic warning", + + /* related to the prologue */ + "no title in document", + "document title should be all caps", + "unknown manual section", + "date missing, using today's date", + "cannot parse date, using it verbatim", + "prologue macros out of order", + "duplicate prologue macro", + "macro not allowed in prologue", + "macro not allowed in body", + + /* related to document structure */ + ".so is fragile, better use ln(1)", + "NAME section must come first", + "bad NAME section contents", + "manual name not yet set", + "sections out of conventional order", + "duplicate section name", + "section not in conventional manual section", + + /* related to macros and nesting */ + "skipping obsolete macro", + "skipping paragraph macro", + "skipping no-space macro", + "blocks badly nested", + "child violates parent syntax", + "nested displays are not portable", + "already in literal mode", + "line scope broken", + + /* related to missing macro arguments */ + "skipping empty macro", + "argument count wrong", + "missing display type", + "list type must come first", + "tag lists require a width argument", + "missing font type", + "skipping end of block that is not open", + + /* related to bad macro arguments */ + "skipping argument", + "duplicate argument", + "duplicate display type", + "duplicate list type", + "unknown AT&T UNIX version", + "bad Boolean value", + "unknown font", + "unknown standard specifier", + "bad width argument", + + /* related to plain text */ + "blank line in non-literal context", + "tab in non-literal context", + "end of line whitespace", + "bad comment style", + "bad escape sequence", + "unterminated quoted string", + + /* related to equations */ + "unexpected literal in equation", + + "generic error", + + /* related to equations */ + "unexpected equation scope closure", + "equation scope open on exit", + "overlapping equation scopes", + "unexpected end of equation", + "equation syntax error", + + /* related to tables */ + "bad table syntax", + "bad table option", + "bad table layout", + "no table layout cells specified", + "no table data cells specified", + "ignore data in cell", + "data block still open", + "ignoring extra data cells", + + "input stack limit exceeded, infinite loop?", + "skipping bad character", + "escaped character not allowed in a name", + "skipping text before the first section header", + "skipping unknown macro", + "NOT IMPLEMENTED, please use groff: skipping request", + "argument count wrong", + "skipping end of block that is not open", + "missing end of block", + "scope open on exit", + "uname(3) system call failed", + "macro requires line argument(s)", + "macro requires body argument(s)", + "macro requires argument(s)", + "missing list type", + "line argument(s) will be lost", + "body argument(s) will be lost", + + "generic fatal error", + + "not a manual", + "column syntax is inconsistent", + "NOT IMPLEMENTED: .Bd -file", + "argument count wrong, violates syntax", + "child violates parent syntax", + "argument count wrong, violates syntax", + "NOT IMPLEMENTED: .so with absolute path or \"..\"", + "no document body", + "no document prologue", + "static buffer exhausted", +}; + +static const char * const mandoclevels[MANDOCLEVEL_MAX] = { + "SUCCESS", + "RESERVED", + "WARNING", + "ERROR", + "FATAL", + "BADARG", + "SYSERR" +}; + +static void +resize_buf(struct buf *buf, size_t initial) +{ + + buf->sz = buf->sz > initial/2 ? 2 * buf->sz : initial; + buf->buf = mandoc_realloc(buf->buf, buf->sz); +} + +static void +pset(const char *buf, int pos, struct mparse *curp) +{ + int i; + + /* + * Try to intuit which kind of manual parser should be used. If + * passed in by command-line (-man, -mdoc), then use that + * explicitly. If passed as -mandoc, then try to guess from the + * line: either skip dot-lines, use -mdoc when finding `.Dt', or + * default to -man, which is more lenient. + * + * Separate out pmdoc/pman from mdoc/man: the first persists + * through all parsers, while the latter is used per-parse. + */ + + if ('.' == buf[0] || '\'' == buf[0]) { + for (i = 1; buf[i]; i++) + if (' ' != buf[i] && '\t' != buf[i]) + break; + if ('\0' == buf[i]) + return; + } + + switch (curp->inttype) { + case (MPARSE_MDOC): + if (NULL == curp->pmdoc) + curp->pmdoc = mdoc_alloc(curp->roff, curp); + assert(curp->pmdoc); + curp->mdoc = curp->pmdoc; + return; + case (MPARSE_MAN): + if (NULL == curp->pman) + curp->pman = man_alloc(curp->roff, curp); + assert(curp->pman); + curp->man = curp->pman; + return; + default: + break; + } + + if (pos >= 3 && 0 == memcmp(buf, ".Dd", 3)) { + if (NULL == curp->pmdoc) + curp->pmdoc = mdoc_alloc(curp->roff, curp); + assert(curp->pmdoc); + curp->mdoc = curp->pmdoc; + return; + } + + if (NULL == curp->pman) + curp->pman = man_alloc(curp->roff, curp); + assert(curp->pman); + curp->man = curp->pman; +} + +/* + * Main parse routine for an opened file. This is called for each + * opened file and simply loops around the full input file, possibly + * nesting (i.e., with `so'). + */ +static void +mparse_buf_r(struct mparse *curp, struct buf blk, int start) +{ + const struct tbl_span *span; + struct buf ln; + enum rofferr rr; + int i, of, rc; + int pos; /* byte number in the ln buffer */ + int lnn; /* line number in the real file */ + unsigned char c; + + memset(&ln, 0, sizeof(struct buf)); + + lnn = curp->line; + pos = 0; + + for (i = 0; i < (int)blk.sz; ) { + if (0 == pos && '\0' == blk.buf[i]) + break; + + if (start) { + curp->line = lnn; + curp->reparse_count = 0; + } + + while (i < (int)blk.sz && (start || '\0' != blk.buf[i])) { + + /* + * When finding an unescaped newline character, + * leave the character loop to process the line. + * Skip a preceding carriage return, if any. + */ + + if ('\r' == blk.buf[i] && i + 1 < (int)blk.sz && + '\n' == blk.buf[i + 1]) + ++i; + if ('\n' == blk.buf[i]) { + ++i; + ++lnn; + break; + } + + /* + * Warn about bogus characters. If you're using + * non-ASCII encoding, you're screwing your + * readers. Since I'd rather this not happen, + * I'll be helpful and replace these characters + * with "?", so we don't display gibberish. + * Note to manual writers: use special characters. + */ + + c = (unsigned char) blk.buf[i]; + + if ( ! (isascii(c) && + (isgraph(c) || isblank(c)))) { + mandoc_msg(MANDOCERR_BADCHAR, curp, + curp->line, pos, NULL); + i++; + if (pos >= (int)ln.sz) + resize_buf(&ln, 256); + ln.buf[pos++] = '?'; + continue; + } + + /* Trailing backslash = a plain char. */ + + if ('\\' != blk.buf[i] || i + 1 == (int)blk.sz) { + if (pos >= (int)ln.sz) + resize_buf(&ln, 256); + ln.buf[pos++] = blk.buf[i++]; + continue; + } + + /* + * Found escape and at least one other character. + * When it's a newline character, skip it. + * When there is a carriage return in between, + * skip that one as well. + */ + + if ('\r' == blk.buf[i + 1] && i + 2 < (int)blk.sz && + '\n' == blk.buf[i + 2]) + ++i; + if ('\n' == blk.buf[i + 1]) { + i += 2; + ++lnn; + continue; + } + + if ('"' == blk.buf[i + 1] || '#' == blk.buf[i + 1]) { + i += 2; + /* Comment, skip to end of line */ + for (; i < (int)blk.sz; ++i) { + if ('\n' == blk.buf[i]) { + ++i; + ++lnn; + break; + } + } + + /* Backout trailing whitespaces */ + for (; pos > 0; --pos) { + if (ln.buf[pos - 1] != ' ') + break; + if (pos > 2 && ln.buf[pos - 2] == '\\') + break; + } + break; + } + + /* Some other escape sequence, copy & cont. */ + + if (pos + 1 >= (int)ln.sz) + resize_buf(&ln, 256); + + ln.buf[pos++] = blk.buf[i++]; + ln.buf[pos++] = blk.buf[i++]; + } + + if (pos >= (int)ln.sz) + resize_buf(&ln, 256); + + ln.buf[pos] = '\0'; + + /* + * A significant amount of complexity is contained by + * the roff preprocessor. It's line-oriented but can be + * expressed on one line, so we need at times to + * readjust our starting point and re-run it. The roff + * preprocessor can also readjust the buffers with new + * data, so we pass them in wholesale. + */ + + of = 0; + + /* + * Maintain a lookaside buffer of all parsed lines. We + * only do this if mparse_keep() has been invoked (the + * buffer may be accessed with mparse_getkeep()). + */ + + if (curp->secondary) { + curp->secondary->buf = + mandoc_realloc + (curp->secondary->buf, + curp->secondary->sz + pos + 2); + memcpy(curp->secondary->buf + + curp->secondary->sz, + ln.buf, pos); + curp->secondary->sz += pos; + curp->secondary->buf + [curp->secondary->sz] = '\n'; + curp->secondary->sz++; + curp->secondary->buf + [curp->secondary->sz] = '\0'; + } +rerun: + rr = roff_parseln + (curp->roff, curp->line, + &ln.buf, &ln.sz, of, &of); + + switch (rr) { + case (ROFF_REPARSE): + if (REPARSE_LIMIT >= ++curp->reparse_count) + mparse_buf_r(curp, ln, 0); + else + mandoc_msg(MANDOCERR_ROFFLOOP, curp, + curp->line, pos, NULL); + pos = 0; + continue; + case (ROFF_APPEND): + pos = (int)strlen(ln.buf); + continue; + case (ROFF_RERUN): + goto rerun; + case (ROFF_IGN): + pos = 0; + continue; + case (ROFF_ERR): + assert(MANDOCLEVEL_FATAL <= curp->file_status); + break; + case (ROFF_SO): + /* + * We remove `so' clauses from our lookaside + * buffer because we're going to descend into + * the file recursively. + */ + if (curp->secondary) + curp->secondary->sz -= pos + 1; + mparse_readfd_r(curp, -1, ln.buf + of, 1); + if (MANDOCLEVEL_FATAL <= curp->file_status) + break; + pos = 0; + continue; + default: + break; + } + + /* + * If we encounter errors in the recursive parse, make + * sure we don't continue parsing. + */ + + if (MANDOCLEVEL_FATAL <= curp->file_status) + break; + + /* + * If input parsers have not been allocated, do so now. + * We keep these instanced between parsers, but set them + * locally per parse routine since we can use different + * parsers with each one. + */ + + if ( ! (curp->man || curp->mdoc)) + pset(ln.buf + of, pos - of, curp); + + /* + * Lastly, push down into the parsers themselves. One + * of these will have already been set in the pset() + * routine. + * If libroff returns ROFF_TBL, then add it to the + * currently open parse. Since we only get here if + * there does exist data (see tbl_data.c), we're + * guaranteed that something's been allocated. + * Do the same for ROFF_EQN. + */ + + rc = -1; + + if (ROFF_TBL == rr) + while (NULL != (span = roff_span(curp->roff))) { + rc = curp->man ? + man_addspan(curp->man, span) : + mdoc_addspan(curp->mdoc, span); + if (0 == rc) + break; + } + else if (ROFF_EQN == rr) + rc = curp->mdoc ? + mdoc_addeqn(curp->mdoc, + roff_eqn(curp->roff)) : + man_addeqn(curp->man, + roff_eqn(curp->roff)); + else if (curp->man || curp->mdoc) + rc = curp->man ? + man_parseln(curp->man, + curp->line, ln.buf, of) : + mdoc_parseln(curp->mdoc, + curp->line, ln.buf, of); + + if (0 == rc) { + assert(MANDOCLEVEL_FATAL <= curp->file_status); + break; + } + + /* Temporary buffers typically are not full. */ + + if (0 == start && '\0' == blk.buf[i]) + break; + + /* Start the next input line. */ + + pos = 0; + } + + free(ln.buf); +} + +static int +read_whole_file(const char *file, int fd, struct buf *fb, int *with_mmap) +{ + size_t off; + ssize_t ssz; + +#ifdef HAVE_MMAP + struct stat st; + if (-1 == fstat(fd, &st)) { + perror(file); + return(0); + } + + /* + * If we're a regular file, try just reading in the whole entry + * via mmap(). This is faster than reading it into blocks, and + * since each file is only a few bytes to begin with, I'm not + * concerned that this is going to tank any machines. + */ + + if (S_ISREG(st.st_mode)) { + if (st.st_size >= (1U << 31)) { + fprintf(stderr, "%s: input too large\n", file); + return(0); + } + *with_mmap = 1; + fb->sz = (size_t)st.st_size; + fb->buf = mmap(NULL, fb->sz, PROT_READ, + MAP_FILE|MAP_SHARED, fd, 0); + if (fb->buf != MAP_FAILED) + return(1); + } +#endif + + /* + * If this isn't a regular file (like, say, stdin), then we must + * go the old way and just read things in bit by bit. + */ + + *with_mmap = 0; + off = 0; + fb->sz = 0; + fb->buf = NULL; + for (;;) { + if (off == fb->sz) { + if (fb->sz == (1U << 31)) { + fprintf(stderr, "%s: input too large\n", file); + break; + } + resize_buf(fb, 65536); + } + ssz = read(fd, fb->buf + (int)off, fb->sz - off); + if (ssz == 0) { + fb->sz = off; + return(1); + } + if (ssz == -1) { + perror(file); + break; + } + off += (size_t)ssz; + } + + free(fb->buf); + fb->buf = NULL; + return(0); +} + +static void +mparse_end(struct mparse *curp) +{ + + if (MANDOCLEVEL_FATAL <= curp->file_status) + return; + + if (curp->mdoc && ! mdoc_endparse(curp->mdoc)) { + assert(MANDOCLEVEL_FATAL <= curp->file_status); + return; + } + + if (curp->man && ! man_endparse(curp->man)) { + assert(MANDOCLEVEL_FATAL <= curp->file_status); + return; + } + + if ( ! (curp->man || curp->mdoc)) { + mandoc_msg(MANDOCERR_NOTMANUAL, curp, 1, 0, NULL); + curp->file_status = MANDOCLEVEL_FATAL; + return; + } + + roff_endparse(curp->roff); +} + +static void +mparse_parse_buffer(struct mparse *curp, struct buf blk, const char *file, + int re) +{ + const char *svfile; + + /* Line number is per-file. */ + svfile = curp->file; + curp->file = file; + curp->line = 1; + + mparse_buf_r(curp, blk, 1); + + if (0 == re && MANDOCLEVEL_FATAL > curp->file_status) + mparse_end(curp); + + curp->file = svfile; +} + +enum mandoclevel +mparse_readmem(struct mparse *curp, const void *buf, size_t len, + const char *file) +{ + struct buf blk; + + blk.buf = UNCONST(buf); + blk.sz = len; + + mparse_parse_buffer(curp, blk, file, 0); + return(curp->file_status); +} + +static void +mparse_readfd_r(struct mparse *curp, int fd, const char *file, int re) +{ + struct buf blk; + int with_mmap; + + if (-1 == fd) + if (-1 == (fd = open(file, O_RDONLY, 0))) { + perror(file); + curp->file_status = MANDOCLEVEL_SYSERR; + return; + } + /* + * Run for each opened file; may be called more than once for + * each full parse sequence if the opened file is nested (i.e., + * from `so'). Simply sucks in the whole file and moves into + * the parse phase for the file. + */ + + if ( ! read_whole_file(file, fd, &blk, &with_mmap)) { + curp->file_status = MANDOCLEVEL_SYSERR; + return; + } + + mparse_parse_buffer(curp, blk, file, re); + +#ifdef HAVE_MMAP + if (with_mmap) + munmap(blk.buf, blk.sz); + else +#endif + free(blk.buf); + + if (STDIN_FILENO != fd && -1 == close(fd)) + perror(file); +} + +enum mandoclevel +mparse_readfd(struct mparse *curp, int fd, const char *file) +{ + + mparse_readfd_r(curp, fd, file, 0); + return(curp->file_status); +} + +struct mparse * +mparse_alloc(enum mparset inttype, enum mandoclevel wlevel, mandocmsg mmsg, void *arg) +{ + struct mparse *curp; + + assert(wlevel <= MANDOCLEVEL_FATAL); + + curp = mandoc_calloc(1, sizeof(struct mparse)); + + curp->wlevel = wlevel; + curp->mmsg = mmsg; + curp->arg = arg; + curp->inttype = inttype; + + curp->roff = roff_alloc(curp); + return(curp); +} + +void +mparse_reset(struct mparse *curp) +{ + + roff_reset(curp->roff); + + if (curp->mdoc) + mdoc_reset(curp->mdoc); + if (curp->man) + man_reset(curp->man); + if (curp->secondary) + curp->secondary->sz = 0; + + curp->file_status = MANDOCLEVEL_OK; + curp->mdoc = NULL; + curp->man = NULL; +} + +void +mparse_free(struct mparse *curp) +{ + + if (curp->pmdoc) + mdoc_free(curp->pmdoc); + if (curp->pman) + man_free(curp->pman); + if (curp->roff) + roff_free(curp->roff); + if (curp->secondary) + free(curp->secondary->buf); + + free(curp->secondary); + free(curp); +} + +void +mparse_result(struct mparse *curp, struct mdoc **mdoc, struct man **man) +{ + + if (mdoc) + *mdoc = curp->mdoc; + if (man) + *man = curp->man; +} + +void +mandoc_vmsg(enum mandocerr t, struct mparse *m, + int ln, int pos, const char *fmt, ...) +{ + char buf[256]; + va_list ap; + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf) - 1, fmt, ap); + va_end(ap); + + mandoc_msg(t, m, ln, pos, buf); +} + +void +mandoc_msg(enum mandocerr er, struct mparse *m, + int ln, int col, const char *msg) +{ + enum mandoclevel level; + + level = MANDOCLEVEL_FATAL; + while (er < mandoclimits[level]) + level--; + + if (level < m->wlevel) + return; + + if (m->mmsg) + (*m->mmsg)(er, level, m->file, ln, col, msg); + + if (m->file_status < level) + m->file_status = level; +} + +const char * +mparse_strerror(enum mandocerr er) +{ + + return(mandocerrs[er]); +} + +const char * +mparse_strlevel(enum mandoclevel lvl) +{ + return(mandoclevels[lvl]); +} + +void +mparse_keep(struct mparse *p) +{ + + assert(NULL == p->secondary); + p->secondary = mandoc_calloc(1, sizeof(struct buf)); +} + +const char * +mparse_getkeep(const struct mparse *p) +{ + + assert(p->secondary); + return(p->secondary->sz ? p->secondary->buf : NULL); +} diff --git a/roff.7 b/roff.7 new file mode 100644 index 000000000000..dd32a45b2908 --- /dev/null +++ b/roff.7 @@ -0,0 +1,989 @@ +.\" $Id: roff.7,v 1.37 2011/12/11 00:38:11 schwarze Exp $ +.\" +.\" Copyright (c) 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> +.\" Copyright (c) 2010, 2011 Ingo Schwarze <schwarze@openbsd.org> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: December 11 2011 $ +.Dt ROFF 7 +.Os +.Sh NAME +.Nm roff +.Nd roff language reference for mandoc +.Sh DESCRIPTION +The +.Nm roff +language is a general purpose text formatting language. +Since traditional implementations of the +.Xr mdoc 7 +and +.Xr man 7 +manual formatting languages are based on it, +many real-world manuals use small numbers of +.Nm +requests intermixed with their +.Xr mdoc 7 +or +.Xr man 7 +code. +To properly format such manuals, the +.Xr mandoc 1 +utility supports a tiny subset of +.Nm +requests. +Only these requests supported by +.Xr mandoc 1 +are documented in the present manual, +together with the basic language syntax shared by +.Nm , +.Xr mdoc 7 , +and +.Xr man 7 . +For complete +.Nm +manuals, consult the +.Sx SEE ALSO +section. +.Pp +Input lines beginning with the control character +.Sq \&. +are parsed for requests and macros. +Such lines are called +.Dq request lines +or +.Dq macro lines , +respectively. +Requests change the processing state and manipulate the formatting; +some macros also define the document structure and produce formatted +output. +The single quote +.Pq Qq \(aq +is accepted as an alternative control character, +treated by +.Xr mandoc 1 +just like +.Ql \&. +.Pp +Lines not beginning with control characters are called +.Dq text lines . +They provide free-form text to be printed; the formatting of the text +depends on the respective processing context. +.Sh LANGUAGE SYNTAX +.Nm +documents may contain only graphable 7-bit ASCII characters, the space +character, and, in certain circumstances, the tab character. +The back-space character +.Sq \e +indicates the start of an escape sequence for +.Sx Comments , +.Sx Special Characters , +.Sx Predefined Strings , +and +user-defined strings defined using the +.Sx ds +request. +.Ss Comments +Text following an escaped double-quote +.Sq \e\(dq , +whether in a request, macro, or text line, is ignored to the end of the line. +A request line beginning with a control character and comment escape +.Sq \&.\e\(dq +is also ignored. +Furthermore, request lines with only a control character and optional +trailing whitespace are stripped from input. +.Pp +Examples: +.Bd -literal -offset indent -compact +\&.\e\(dq This is a comment line. +\&.\e\(dq The next line is ignored: +\&. +\&.Sh EXAMPLES \e\(dq This is a comment, too. +\&example text \e\(dq And so is this. +.Ed +.Ss Special Characters +Special characters are used to encode special glyphs and are rendered +differently across output media. +They may occur in request, macro, and text lines. +Sequences begin with the escape character +.Sq \e +followed by either an open-parenthesis +.Sq \&( +for two-character sequences; an open-bracket +.Sq \&[ +for n-character sequences (terminated at a close-bracket +.Sq \&] ) ; +or a single one character sequence. +.Pp +Examples: +.Bl -tag -width Ds -offset indent -compact +.It Li \e(em +Two-letter em dash escape. +.It Li \ee +One-letter backslash escape. +.El +.Pp +See +.Xr mandoc_char 7 +for a complete list. +.Ss Text Decoration +Terms may be text-decorated using the +.Sq \ef +escape followed by an indicator: B (bold), I (italic), R (regular), or P +(revert to previous mode). +A numerical representation 3, 2, or 1 (bold, italic, and regular, +respectively) may be used instead. +The indicator or numerical representative may be preceded by C +(constant-width), which is ignored. +.Pp +Examples: +.Bl -tag -width Ds -offset indent -compact +.It Li \efBbold\efR +Write in bold, then switch to regular font mode. +.It Li \efIitalic\efP +Write in italic, then return to previous font mode. +.El +.Pp +Text decoration is +.Em not +recommended for +.Xr mdoc 7 , +which encourages semantic annotation. +.Ss Predefined Strings +Predefined strings, like +.Sx Special Characters , +mark special output glyphs. +Predefined strings are escaped with the slash-asterisk, +.Sq \e* : +single-character +.Sq \e*X , +two-character +.Sq \e*(XX , +and N-character +.Sq \e*[N] . +.Pp +Examples: +.Bl -tag -width Ds -offset indent -compact +.It Li \e*(Am +Two-letter ampersand predefined string. +.It Li \e*q +One-letter double-quote predefined string. +.El +.Pp +Predefined strings are not recommended for use, +as they differ across implementations. +Those supported by +.Xr mandoc 1 +are listed in +.Xr mandoc_char 7 . +Manuals using these predefined strings are almost certainly not portable. +.Ss Whitespace +Whitespace consists of the space character. +In text lines, whitespace is preserved within a line. +In request and macro lines, whitespace delimits arguments and is discarded. +.Pp +Unescaped trailing spaces are stripped from text line input unless in a +literal context. +In general, trailing whitespace on any input line is discouraged for +reasons of portability. +In the rare case that a blank character is needed at the end of an +input line, it may be forced by +.Sq \e\ \e& . +.Pp +Literal space characters can be produced in the output +using escape sequences. +In macro lines, they can also be included in arguments using quotation; see +.Sx MACRO SYNTAX +for details. +.Pp +Blank text lines, which may include whitespace, are only permitted +within literal contexts. +If the first character of a text line is a space, that line is printed +with a leading newline. +.Ss Scaling Widths +Many requests and macros support scaled widths for their arguments. +The syntax for a scaled width is +.Sq Li [+-]?[0-9]*.[0-9]*[:unit:] , +where a decimal must be preceded or followed by at least one digit. +Negative numbers, while accepted, are truncated to zero. +.Pp +The following scaling units are accepted: +.Pp +.Bl -tag -width Ds -offset indent -compact +.It c +centimetre +.It i +inch +.It P +pica (~1/6 inch) +.It p +point (~1/72 inch) +.It f +synonym for +.Sq u +.It v +default vertical span +.It m +width of rendered +.Sq m +.Pq em +character +.It n +width of rendered +.Sq n +.Pq en +character +.It u +default horizontal span +.It M +mini-em (~1/100 em) +.El +.Pp +Using anything other than +.Sq m , +.Sq n , +.Sq u , +or +.Sq v +is necessarily non-portable across output media. +See +.Sx COMPATIBILITY . +.Pp +If a scaling unit is not provided, the numerical value is interpreted +under the default rules of +.Sq v +for vertical spaces and +.Sq u +for horizontal ones. +.Pp +Examples: +.Bl -tag -width ".Bl -tag -width 2i" -offset indent -compact +.It Li \&.Bl -tag -width 2i +two-inch tagged list indentation in +.Xr mdoc 7 +.It Li \&.HP 2i +two-inch tagged list indentation in +.Xr man 7 +.It Li \&.sp 2v +two vertical spaces +.El +.Ss Sentence Spacing +Each sentence should terminate at the end of an input line. +By doing this, a formatter will be able to apply the proper amount of +spacing after the end of sentence (unescaped) period, exclamation mark, +or question mark followed by zero or more non-sentence closing +delimiters +.Po +.Sq \&) , +.Sq \&] , +.Sq \&' , +.Sq \&" +.Pc . +.Pp +The proper spacing is also intelligently preserved if a sentence ends at +the boundary of a macro line. +.Pp +Examples: +.Bd -literal -offset indent -compact +Do not end sentences mid-line like this. Instead, +end a sentence like this. +A macro would end like this: +\&.Xr mandoc 1 \&. +.Ed +.Sh REQUEST SYNTAX +A request or macro line consists of: +.Pp +.Bl -enum -compact +.It +the control character +.Sq \&. +or +.Sq \(aq +at the beginning of the line, +.It +optionally an arbitrary amount of whitespace, +.It +the name of the request or the macro, which is one word of arbitrary +length, terminated by whitespace, +.It +and zero or more arguments delimited by whitespace. +.El +.Pp +Thus, the following request lines are all equivalent: +.Bd -literal -offset indent +\&.ig end +\&.ig end +\&. ig end +.Ed +.Sh MACRO SYNTAX +Macros are provided by the +.Xr mdoc 7 +and +.Xr man 7 +languages and can be defined by the +.Sx \&de +request. +When called, they follow the same syntax as requests, except that +macro arguments may optionally be quoted by enclosing them +in double quote characters +.Pq Sq \(dq . +Quoted text, even if it contains whitespace or would cause +a macro invocation when unquoted, is always considered literal text. +Inside quoted text, pairs of double quote characters +.Pq Sq Qq +resolve to single double quote characters. +.Pp +To be recognised as the beginning of a quoted argument, the opening +quote character must be preceded by a space character. +A quoted argument extends to the next double quote character that is not +part of a pair, or to the end of the input line, whichever comes earlier. +Leaving out the terminating double quote character at the end of the line +is discouraged. +For clarity, if more arguments follow on the same input line, +it is recommended to follow the terminating double quote character +by a space character; in case the next character after the terminating +double quote character is anything else, it is regarded as the beginning +of the next, unquoted argument. +.Pp +Both in quoted and unquoted arguments, pairs of backslashes +.Pq Sq \e\e +resolve to single backslashes. +In unquoted arguments, space characters can alternatively be included +by preceding them with a backslash +.Pq Sq \e\~ , +but quoting is usually better for clarity. +.Pp +Examples: +.Bl -tag -width Ds -offset indent -compact +.It Li .Fn strlen \(dqconst char *s\(dq +Group arguments +.Qq const char *s +into one function argument. +If unspecified, +.Qq const , +.Qq char , +and +.Qq *s +would be considered separate arguments. +.It Li .Op \(dqFl a\(dq +Consider +.Qq \&Fl a +as literal text instead of a flag macro. +.El +.Sh REQUEST REFERENCE +The +.Xr mandoc 1 +.Nm +parser recognises the following requests. +Note that the +.Nm +language defines many more requests not implemented in +.Xr mandoc 1 . +.Ss \&ad +Set line adjustment mode. +This line-scoped request is intended to have one argument to select +normal, left, right, or centre adjustment for subsequent text. +Currently, it is ignored including its arguments, +and the number of arguments is not checked. +.Ss \&am +Append to a macro definition. +The syntax of this request is the same as that of +.Sx \&de . +It is currently ignored by +.Xr mandoc 1 , +as are its children. +.Ss \&ami +Append to a macro definition, specifying the macro name indirectly. +The syntax of this request is the same as that of +.Sx \&dei . +It is currently ignored by +.Xr mandoc 1 , +as are its children. +.Ss \&am1 +Append to a macro definition, switching roff compatibility mode off +during macro execution. +The syntax of this request is the same as that of +.Sx \&de1 . +It is currently ignored by +.Xr mandoc 1 , +as are its children. +.Ss \&de +Define a +.Nm +macro. +Its syntax can be either +.Bd -literal -offset indent +.Pf . Cm \&de Ar name +.Ar macro definition +\&.. +.Ed +.Pp +or +.Bd -literal -offset indent +.Pf . Cm \&de Ar name Ar end +.Ar macro definition +.Pf . Ar end +.Ed +.Pp +Both forms define or redefine the macro +.Ar name +to represent the +.Ar macro definition , +which may consist of one or more input lines, including the newline +characters terminating each line, optionally containing calls to +.Nm +requests, +.Nm +macros or high-level macros like +.Xr man 7 +or +.Xr mdoc 7 +macros, whichever applies to the document in question. +.Pp +Specifying a custom +.Ar end +macro works in the same way as for +.Sx \&ig ; +namely, the call to +.Sq Pf . Ar end +first ends the +.Ar macro definition , +and after that, it is also evaluated as a +.Nm +request or +.Nm +macro, but not as a high-level macro. +.Pp +The macro can be invoked later using the syntax +.Pp +.D1 Pf . Ar name Op Ar argument Op Ar argument ... +.Pp +Regarding argument parsing, see +.Sx MACRO SYNTAX +above. +.Pp +The line invoking the macro will be replaced +in the input stream by the +.Ar macro definition , +replacing all occurrences of +.No \e\e$ Ns Ar N , +where +.Ar N +is a digit, by the +.Ar N Ns th Ar argument . +For example, +.Bd -literal -offset indent +\&.de ZN +\efI\e^\e\e$1\e^\efP\e\e$2 +\&.. +\&.ZN XtFree . +.Ed +.Pp +produces +.Pp +.D1 \efI\e^XtFree\e^\efP. +.Pp +in the input stream, and thus in the output: \fI\^XtFree\^\fP. +.Pp +Since macros and user-defined strings share a common string table, +defining a macro +.Ar name +clobbers the user-defined string +.Ar name , +and the +.Ar macro definition +can also be printed using the +.Sq \e* +string interpolation syntax described below +.Sx ds , +but this is rarely useful because every macro definition contains at least +one explicit newline character. +.Pp +In order to prevent endless recursion, both groff and +.Xr mandoc 1 +limit the stack depth for expanding macros and strings +to a large, but finite number. +Do not rely on the exact value of this limit. +.Ss \&dei +Define a +.Nm +macro, specifying the macro name indirectly. +The syntax of this request is the same as that of +.Sx \&de . +It is currently ignored by +.Xr mandoc 1 , +as are its children. +.Ss \&de1 +Define a +.Nm +macro that will be executed with +.Nm +compatibility mode switched off during macro execution. +This is a GNU extension not available in traditional +.Nm +implementations and not even in older versions of groff. +Since +.Xr mandoc 1 +does not implement +.Nm +compatibility mode at all, it handles this request as an alias for +.Sx \&de . +.Ss \&ds +Define a user-defined string. +Its syntax is as follows: +.Pp +.D1 Pf . Cm \&ds Ar name Oo \(dq Oc Ns Ar string +.Pp +The +.Ar name +and +.Ar string +arguments are space-separated. +If the +.Ar string +begins with a double-quote character, that character will not be part +of the string. +All remaining characters on the input line form the +.Ar string , +including whitespace and double-quote characters, even trailing ones. +.Pp +The +.Ar string +can be interpolated into subsequent text by using +.No \e* Ns Bq Ar name +for a +.Ar name +of arbitrary length, or \e*(NN or \e*N if the length of +.Ar name +is two or one characters, respectively. +Interpolation can be prevented by escaping the leading backslash; +that is, an asterisk preceded by an even number of backslashes +does not trigger string interpolation. +.Pp +Since user-defined strings and macros share a common string table, +defining a string +.Ar name +clobbers the macro +.Ar name , +and the +.Ar name +used for defining a string can also be invoked as a macro, +in which case the following input line will be appended to the +.Ar string , +forming a new input line passed to the +.Nm +parser. +For example, +.Bd -literal -offset indent +\&.ds badidea .S +\&.badidea +H SYNOPSIS +.Ed +.Pp +invokes the +.Cm SH +macro when used in a +.Xr man 7 +document. +Such abuse is of course strongly discouraged. +.Ss \&el +The +.Qq else +half of an if/else conditional. +Pops a result off the stack of conditional evaluations pushed by +.Sx \&ie +and uses it as its conditional. +If no stack entries are present (e.g., due to no prior +.Sx \&ie +calls) +then false is assumed. +The syntax of this request is similar to +.Sx \&if +except that the conditional is missing. +.Ss \&EN +End an equation block. +See +.Sx \&EQ . +.Ss \&EQ +Begin an equation block. +See +.Xr eqn 7 +for a description of the equation language. +.Ss \&hy +Set automatic hyphenation mode. +This line-scoped request is currently ignored. +.Ss \&ie +The +.Qq if +half of an if/else conditional. +The result of the conditional is pushed into a stack used by subsequent +invocations of +.Sx \&el , +which may be separated by any intervening input (or not exist at all). +Its syntax is equivalent to +.Sx \&if . +.Ss \&if +Begins a conditional. +Right now, the conditional evaluates to true +if and only if it starts with the letter +.Sy n , +indicating processing in nroff style as opposed to troff style. +If a conditional is false, its children are not processed, but are +syntactically interpreted to preserve the integrity of the input +document. +Thus, +.Pp +.D1 \&.if t .ig +.Pp +will discard the +.Sq \&.ig , +which may lead to interesting results, but +.Pp +.D1 \&.if t .if t \e{\e +.Pp +will continue to syntactically interpret to the block close of the final +conditional. +Sub-conditionals, in this case, obviously inherit the truth value of +the parent. +This request has the following syntax: +.Bd -literal -offset indent +\&.if COND \e{\e +BODY... +\&.\e} +.Ed +.Bd -literal -offset indent +\&.if COND \e{ BODY +BODY... \e} +.Ed +.Bd -literal -offset indent +\&.if COND \e{ BODY +BODY... +\&.\e} +.Ed +.Bd -literal -offset indent +\&.if COND \e +BODY +.Ed +.Pp +COND is a conditional statement. +roff allows for complicated conditionals; mandoc is much simpler. +At this time, mandoc supports only +.Sq n , +evaluating to true; +and +.Sq t , +.Sq e , +and +.Sq o , +evaluating to false. +All other invocations are read up to the next end of line or space and +evaluate as false. +.Pp +If the BODY section is begun by an escaped brace +.Sq \e{ , +scope continues until a closing-brace escape sequence +.Sq \.\e} . +If the BODY is not enclosed in braces, scope continues until +the end of the line. +If the COND is followed by a BODY on the same line, whether after a +brace or not, then requests and macros +.Em must +begin with a control character. +It is generally more intuitive, in this case, to write +.Bd -literal -offset indent +\&.if COND \e{\e +\&.foo +bar +\&.\e} +.Ed +.Pp +than having the request or macro follow as +.Pp +.D1 \&.if COND \e{ .foo +.Pp +The scope of a conditional is always parsed, but only executed if the +conditional evaluates to true. +.Pp +Note that the +.Sq \e} +is converted into a zero-width escape sequence if not passed as a +standalone macro +.Sq \&.\e} . +For example, +.Pp +.D1 \&.Fl a \e} b +.Pp +will result in +.Sq \e} +being considered an argument of the +.Sq \&Fl +macro. +.Ss \&ig +Ignore input. +Its syntax can be either +.Bd -literal -offset indent +.Pf . Cm \&ig +.Ar ignored text +\&.. +.Ed +.Pp +or +.Bd -literal -offset indent +.Pf . Cm \&ig Ar end +.Ar ignored text +.Pf . Ar end +.Ed +.Pp +In the first case, input is ignored until a +.Sq \&.. +request is encountered on its own line. +In the second case, input is ignored until the specified +.Sq Pf . Ar end +macro is encountered. +Do not use the escape character +.Sq \e +anywhere in the definition of +.Ar end ; +it would cause very strange behaviour. +.Pp +When the +.Ar end +macro is a roff request or a roff macro, like in +.Pp +.D1 \&.ig if +.Pp +the subsequent invocation of +.Sx \&if +will first terminate the +.Ar ignored text , +then be invoked as usual. +Otherwise, it only terminates the +.Ar ignored text , +and arguments following it or the +.Sq \&.. +request are discarded. +.Ss \&ne +Declare the need for the specified minimum vertical space +before the next trap or the bottom of the page. +This line-scoped request is currently ignored. +.Ss \&nh +Turn off automatic hyphenation mode. +This line-scoped request is currently ignored. +.Ss \&rm +Remove a request, macro or string. +This request is intended to have one argument, +the name of the request, macro or string to be undefined. +Currently, it is ignored including its arguments, +and the number of arguments is not checked. +.Ss \&nr +Define a register. +A register is an arbitrary string value that defines some sort of state, +which influences parsing and/or formatting. +Its syntax is as follows: +.Pp +.D1 Pf \. Cm \&nr Ar name Ar value +.Pp +The +.Ar value +may, at the moment, only be an integer. +So far, only the following register +.Ar name +is recognised: +.Bl -tag -width Ds +.It Cm nS +If set to a positive integer value, certain +.Xr mdoc 7 +macros will behave in the same way as in the +.Em SYNOPSIS +section. +If set to 0, these macros will behave in the same way as outside the +.Em SYNOPSIS +section, even when called within the +.Em SYNOPSIS +section itself. +Note that starting a new +.Xr mdoc 7 +section with the +.Cm \&Sh +macro will reset this register. +.El +.Ss \&ns +Turn on no-space mode. +This line-scoped request is intended to take no arguments. +Currently, it is ignored including its arguments, +and the number of arguments is not checked. +.Ss \&ps +Change point size. +This line-scoped request is intended to take one numerical argument. +Currently, it is ignored including its arguments, +and the number of arguments is not checked. +.Ss \&so +Include a source file. +Its syntax is as follows: +.Pp +.D1 Pf \. Cm \&so Ar file +.Pp +The +.Ar file +will be read and its contents processed as input in place of the +.Sq \&.so +request line. +To avoid inadvertent inclusion of unrelated files, +.Xr mandoc 1 +only accepts relative paths not containing the strings +.Qq ../ +and +.Qq /.. . +.Pp +This request requires +.Xr man 1 +to change to the right directory before calling +.Xr mandoc 1 , +per convention to the root of the manual tree. +Typical usage looks like: +.Pp +.Dl \&.so man3/Xcursor.3 +.Pp +As the whole concept is rather fragile, the use of +.Sx \&so +is discouraged. +Use +.Xr ln 1 +instead. +.Ss \&ta +Set tab stops. +This line-scoped request can take an arbitrary number of arguments. +Currently, it is ignored including its arguments. +.Ss \&tr +Output character translation. +Its syntax is as follows: +.Pp +.D1 Pf \. Cm \&tr Ar [ab]+ +.Pp +Pairs of +.Ar ab +characters are replaced +.Ar ( a +for +.Ar b ) . +Replacement (or origin) characters may also be character escapes; thus, +.Pp +.Dl tr \e(xx\e(yy +.Pp +replaces all invocations of \e(xx with \e(yy. +.Ss \&T& +Re-start a table layout, retaining the options of the prior table +invocation. +See +.Sx \&TS . +.Ss \&TE +End a table context. +See +.Sx \&TS . +.Ss \&TS +Begin a table, which formats input in aligned rows and columns. +See +.Xr tbl 7 +for a description of the tbl language. +.Sh COMPATIBILITY +This section documents compatibility between mandoc and other other +.Nm +implementations, at this time limited to GNU troff +.Pq Qq groff . +The term +.Qq historic groff +refers to groff version 1.15. +.Pp +.Bl -dash -compact +.It +In mandoc, the +.Sx \&EQ , +.Sx \&TE , +.Sx \&TS , +and +.Sx \&T& , +macros are considered regular macros. +In all other +.Nm +implementations, these are special macros that must be specified without +spacing between the control character (which must be a period) and the +macro name. +.It +The +.Cm nS +register is only compatible with OpenBSD's groff-1.15. +.It +Historic groff did not accept white-space before a custom +.Ar end +macro for the +.Sx \&ig +request. +.It +The +.Sx \&if +and family would print funny white-spaces with historic groff when +using the next-line syntax. +.El +.Sh SEE ALSO +.Xr mandoc 1 , +.Xr eqn 7 , +.Xr man 7 , +.Xr mandoc_char 7 , +.Xr mdoc 7 , +.Xr tbl 7 +.Rs +.%A Joseph F. Ossanna +.%A Brian W. Kernighan +.%I AT&T Bell Laboratories +.%T Troff User's Manual +.%R Computing Science Technical Report +.%N 54 +.%C Murray Hill, New Jersey +.%D 1976 and 1992 +.%U http://www.kohala.com/start/troff/cstr54.ps +.Re +.Rs +.%A Joseph F. Ossanna +.%A Brian W. Kernighan +.%A Gunnar Ritter +.%T Heirloom Documentation Tools Nroff/Troff User's Manual +.%D September 17, 2007 +.%U http://heirloom.sourceforge.net/doctools/troff.pdf +.Re +.Sh HISTORY +The RUNOFF typesetting system, whose input forms the basis for +.Nm , +was written in MAD and FAP for the CTSS operating system by Jerome E. +Saltzer in 1964. +Doug McIlroy rewrote it in BCPL in 1969, renaming it +.Nm . +Dennis M. Ritchie rewrote McIlroy's +.Nm +in PDP-11 assembly for +.At v1 , +Joseph F. Ossanna improved roff and renamed it nroff +for +.At v2 , +then ported nroff to C as troff, which Brian W. Kernighan released with +.At v7 . +In 1989, James Clarke re-implemented troff in C++, naming it groff. +.Sh AUTHORS +.An -nosplit +This +.Nm +reference was written by +.An Kristaps Dzonsons , +.Mt kristaps@bsd.lv ; +and +.An Ingo Schwarze , +.Mt schwarze@openbsd.org . diff --git a/roff.c b/roff.c new file mode 100644 index 000000000000..b479cc298cff --- /dev/null +++ b/roff.c @@ -0,0 +1,1768 @@ +/* $Id: roff.c,v 1.172 2011/10/24 21:41:45 schwarze Exp $ */ +/* + * Copyright (c) 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * Copyright (c) 2010, 2011 Ingo Schwarze <schwarze@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <assert.h> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> + +#include "mandoc.h" +#include "libroff.h" +#include "libmandoc.h" + +/* Maximum number of nested if-else conditionals. */ +#define RSTACK_MAX 128 + +/* Maximum number of string expansions per line, to break infinite loops. */ +#define EXPAND_LIMIT 1000 + +enum rofft { + ROFF_ad, + ROFF_am, + ROFF_ami, + ROFF_am1, + ROFF_de, + ROFF_dei, + ROFF_de1, + ROFF_ds, + ROFF_el, + ROFF_hy, + ROFF_ie, + ROFF_if, + ROFF_ig, + ROFF_it, + ROFF_ne, + ROFF_nh, + ROFF_nr, + ROFF_ns, + ROFF_ps, + ROFF_rm, + ROFF_so, + ROFF_ta, + ROFF_tr, + ROFF_TS, + ROFF_TE, + ROFF_T_, + ROFF_EQ, + ROFF_EN, + ROFF_cblock, + ROFF_ccond, + ROFF_USERDEF, + ROFF_MAX +}; + +enum roffrule { + ROFFRULE_ALLOW, + ROFFRULE_DENY +}; + +/* + * A single register entity. If "set" is zero, the value of the + * register should be the default one, which is per-register. + * Registers are assumed to be unsigned ints for now. + */ +struct reg { + int set; /* whether set or not */ + unsigned int u; /* unsigned integer */ +}; + +/* + * An incredibly-simple string buffer. + */ +struct roffstr { + char *p; /* nil-terminated buffer */ + size_t sz; /* saved strlen(p) */ +}; + +/* + * A key-value roffstr pair as part of a singly-linked list. + */ +struct roffkv { + struct roffstr key; + struct roffstr val; + struct roffkv *next; /* next in list */ +}; + +struct roff { + struct mparse *parse; /* parse point */ + struct roffnode *last; /* leaf of stack */ + enum roffrule rstack[RSTACK_MAX]; /* stack of !`ie' rules */ + int rstackpos; /* position in rstack */ + struct reg regs[REG__MAX]; + struct roffkv *strtab; /* user-defined strings & macros */ + struct roffkv *xmbtab; /* multi-byte trans table (`tr') */ + struct roffstr *xtab; /* single-byte trans table (`tr') */ + const char *current_string; /* value of last called user macro */ + struct tbl_node *first_tbl; /* first table parsed */ + struct tbl_node *last_tbl; /* last table parsed */ + struct tbl_node *tbl; /* current table being parsed */ + struct eqn_node *last_eqn; /* last equation parsed */ + struct eqn_node *first_eqn; /* first equation parsed */ + struct eqn_node *eqn; /* current equation being parsed */ +}; + +struct roffnode { + enum rofft tok; /* type of node */ + struct roffnode *parent; /* up one in stack */ + int line; /* parse line */ + int col; /* parse col */ + char *name; /* node name, e.g. macro name */ + char *end; /* end-rules: custom token */ + int endspan; /* end-rules: next-line or infty */ + enum roffrule rule; /* current evaluation rule */ +}; + +#define ROFF_ARGS struct roff *r, /* parse ctx */ \ + enum rofft tok, /* tok of macro */ \ + char **bufp, /* input buffer */ \ + size_t *szp, /* size of input buffer */ \ + int ln, /* parse line */ \ + int ppos, /* original pos in buffer */ \ + int pos, /* current pos in buffer */ \ + int *offs /* reset offset of buffer data */ + +typedef enum rofferr (*roffproc)(ROFF_ARGS); + +struct roffmac { + const char *name; /* macro name */ + roffproc proc; /* process new macro */ + roffproc text; /* process as child text of macro */ + roffproc sub; /* process as child of macro */ + int flags; +#define ROFFMAC_STRUCT (1 << 0) /* always interpret */ + struct roffmac *next; +}; + +struct predef { + const char *name; /* predefined input name */ + const char *str; /* replacement symbol */ +}; + +#define PREDEF(__name, __str) \ + { (__name), (__str) }, + +static enum rofft roffhash_find(const char *, size_t); +static void roffhash_init(void); +static void roffnode_cleanscope(struct roff *); +static void roffnode_pop(struct roff *); +static void roffnode_push(struct roff *, enum rofft, + const char *, int, int); +static enum rofferr roff_block(ROFF_ARGS); +static enum rofferr roff_block_text(ROFF_ARGS); +static enum rofferr roff_block_sub(ROFF_ARGS); +static enum rofferr roff_cblock(ROFF_ARGS); +static enum rofferr roff_ccond(ROFF_ARGS); +static enum rofferr roff_cond(ROFF_ARGS); +static enum rofferr roff_cond_text(ROFF_ARGS); +static enum rofferr roff_cond_sub(ROFF_ARGS); +static enum rofferr roff_ds(ROFF_ARGS); +static enum roffrule roff_evalcond(const char *, int *); +static void roff_free1(struct roff *); +static void roff_freestr(struct roffkv *); +static char *roff_getname(struct roff *, char **, int, int); +static const char *roff_getstrn(const struct roff *, + const char *, size_t); +static enum rofferr roff_line_ignore(ROFF_ARGS); +static enum rofferr roff_nr(ROFF_ARGS); +static void roff_openeqn(struct roff *, const char *, + int, int, const char *); +static enum rofft roff_parse(struct roff *, const char *, int *); +static enum rofferr roff_parsetext(char *); +static enum rofferr roff_res(struct roff *, + char **, size_t *, int, int); +static enum rofferr roff_rm(ROFF_ARGS); +static void roff_setstr(struct roff *, + const char *, const char *, int); +static void roff_setstrn(struct roffkv **, const char *, + size_t, const char *, size_t, int); +static enum rofferr roff_so(ROFF_ARGS); +static enum rofferr roff_tr(ROFF_ARGS); +static enum rofferr roff_TE(ROFF_ARGS); +static enum rofferr roff_TS(ROFF_ARGS); +static enum rofferr roff_EQ(ROFF_ARGS); +static enum rofferr roff_EN(ROFF_ARGS); +static enum rofferr roff_T_(ROFF_ARGS); +static enum rofferr roff_userdef(ROFF_ARGS); + +/* See roffhash_find() */ + +#define ASCII_HI 126 +#define ASCII_LO 33 +#define HASHWIDTH (ASCII_HI - ASCII_LO + 1) + +static struct roffmac *hash[HASHWIDTH]; + +static struct roffmac roffs[ROFF_MAX] = { + { "ad", roff_line_ignore, NULL, NULL, 0, NULL }, + { "am", roff_block, roff_block_text, roff_block_sub, 0, NULL }, + { "ami", roff_block, roff_block_text, roff_block_sub, 0, NULL }, + { "am1", roff_block, roff_block_text, roff_block_sub, 0, NULL }, + { "de", roff_block, roff_block_text, roff_block_sub, 0, NULL }, + { "dei", roff_block, roff_block_text, roff_block_sub, 0, NULL }, + { "de1", roff_block, roff_block_text, roff_block_sub, 0, NULL }, + { "ds", roff_ds, NULL, NULL, 0, NULL }, + { "el", roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT, NULL }, + { "hy", roff_line_ignore, NULL, NULL, 0, NULL }, + { "ie", roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT, NULL }, + { "if", roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT, NULL }, + { "ig", roff_block, roff_block_text, roff_block_sub, 0, NULL }, + { "it", roff_line_ignore, NULL, NULL, 0, NULL }, + { "ne", roff_line_ignore, NULL, NULL, 0, NULL }, + { "nh", roff_line_ignore, NULL, NULL, 0, NULL }, + { "nr", roff_nr, NULL, NULL, 0, NULL }, + { "ns", roff_line_ignore, NULL, NULL, 0, NULL }, + { "ps", roff_line_ignore, NULL, NULL, 0, NULL }, + { "rm", roff_rm, NULL, NULL, 0, NULL }, + { "so", roff_so, NULL, NULL, 0, NULL }, + { "ta", roff_line_ignore, NULL, NULL, 0, NULL }, + { "tr", roff_tr, NULL, NULL, 0, NULL }, + { "TS", roff_TS, NULL, NULL, 0, NULL }, + { "TE", roff_TE, NULL, NULL, 0, NULL }, + { "T&", roff_T_, NULL, NULL, 0, NULL }, + { "EQ", roff_EQ, NULL, NULL, 0, NULL }, + { "EN", roff_EN, NULL, NULL, 0, NULL }, + { ".", roff_cblock, NULL, NULL, 0, NULL }, + { "\\}", roff_ccond, NULL, NULL, 0, NULL }, + { NULL, roff_userdef, NULL, NULL, 0, NULL }, +}; + +/* Array of injected predefined strings. */ +#define PREDEFS_MAX 38 +static const struct predef predefs[PREDEFS_MAX] = { +#include "predefs.in" +}; + +/* See roffhash_find() */ +#define ROFF_HASH(p) (p[0] - ASCII_LO) + +static void +roffhash_init(void) +{ + struct roffmac *n; + int buc, i; + + for (i = 0; i < (int)ROFF_USERDEF; i++) { + assert(roffs[i].name[0] >= ASCII_LO); + assert(roffs[i].name[0] <= ASCII_HI); + + buc = ROFF_HASH(roffs[i].name); + + if (NULL != (n = hash[buc])) { + for ( ; n->next; n = n->next) + /* Do nothing. */ ; + n->next = &roffs[i]; + } else + hash[buc] = &roffs[i]; + } +} + +/* + * Look up a roff token by its name. Returns ROFF_MAX if no macro by + * the nil-terminated string name could be found. + */ +static enum rofft +roffhash_find(const char *p, size_t s) +{ + int buc; + struct roffmac *n; + + /* + * libroff has an extremely simple hashtable, for the time + * being, which simply keys on the first character, which must + * be printable, then walks a chain. It works well enough until + * optimised. + */ + + if (p[0] < ASCII_LO || p[0] > ASCII_HI) + return(ROFF_MAX); + + buc = ROFF_HASH(p); + + if (NULL == (n = hash[buc])) + return(ROFF_MAX); + for ( ; n; n = n->next) + if (0 == strncmp(n->name, p, s) && '\0' == n->name[(int)s]) + return((enum rofft)(n - roffs)); + + return(ROFF_MAX); +} + + +/* + * Pop the current node off of the stack of roff instructions currently + * pending. + */ +static void +roffnode_pop(struct roff *r) +{ + struct roffnode *p; + + assert(r->last); + p = r->last; + + r->last = r->last->parent; + free(p->name); + free(p->end); + free(p); +} + + +/* + * Push a roff node onto the instruction stack. This must later be + * removed with roffnode_pop(). + */ +static void +roffnode_push(struct roff *r, enum rofft tok, const char *name, + int line, int col) +{ + struct roffnode *p; + + p = mandoc_calloc(1, sizeof(struct roffnode)); + p->tok = tok; + if (name) + p->name = mandoc_strdup(name); + p->parent = r->last; + p->line = line; + p->col = col; + p->rule = p->parent ? p->parent->rule : ROFFRULE_DENY; + + r->last = p; +} + + +static void +roff_free1(struct roff *r) +{ + struct tbl_node *t; + struct eqn_node *e; + int i; + + while (NULL != (t = r->first_tbl)) { + r->first_tbl = t->next; + tbl_free(t); + } + + r->first_tbl = r->last_tbl = r->tbl = NULL; + + while (NULL != (e = r->first_eqn)) { + r->first_eqn = e->next; + eqn_free(e); + } + + r->first_eqn = r->last_eqn = r->eqn = NULL; + + while (r->last) + roffnode_pop(r); + + roff_freestr(r->strtab); + roff_freestr(r->xmbtab); + + r->strtab = r->xmbtab = NULL; + + if (r->xtab) + for (i = 0; i < 128; i++) + free(r->xtab[i].p); + + free(r->xtab); + r->xtab = NULL; +} + +void +roff_reset(struct roff *r) +{ + int i; + + roff_free1(r); + + memset(&r->regs, 0, sizeof(struct reg) * REG__MAX); + + for (i = 0; i < PREDEFS_MAX; i++) + roff_setstr(r, predefs[i].name, predefs[i].str, 0); +} + + +void +roff_free(struct roff *r) +{ + + roff_free1(r); + free(r); +} + + +struct roff * +roff_alloc(struct mparse *parse) +{ + struct roff *r; + int i; + + r = mandoc_calloc(1, sizeof(struct roff)); + r->parse = parse; + r->rstackpos = -1; + + roffhash_init(); + + for (i = 0; i < PREDEFS_MAX; i++) + roff_setstr(r, predefs[i].name, predefs[i].str, 0); + + return(r); +} + +/* + * Pre-filter each and every line for reserved words (one beginning with + * `\*', e.g., `\*(ab'). These must be handled before the actual line + * is processed. + * This also checks the syntax of regular escapes. + */ +static enum rofferr +roff_res(struct roff *r, char **bufp, size_t *szp, int ln, int pos) +{ + enum mandoc_esc esc; + const char *stesc; /* start of an escape sequence ('\\') */ + const char *stnam; /* start of the name, after "[(*" */ + const char *cp; /* end of the name, e.g. before ']' */ + const char *res; /* the string to be substituted */ + int i, maxl, expand_count; + size_t nsz; + char *n; + + expand_count = 0; + +again: + cp = *bufp + pos; + while (NULL != (cp = strchr(cp, '\\'))) { + stesc = cp++; + + /* + * The second character must be an asterisk. + * If it isn't, skip it anyway: It is escaped, + * so it can't start another escape sequence. + */ + + if ('\0' == *cp) + return(ROFF_CONT); + + if ('*' != *cp) { + res = cp; + esc = mandoc_escape(&cp, NULL, NULL); + if (ESCAPE_ERROR != esc) + continue; + cp = res; + mandoc_msg + (MANDOCERR_BADESCAPE, r->parse, + ln, (int)(stesc - *bufp), NULL); + return(ROFF_CONT); + } + + cp++; + + /* + * The third character decides the length + * of the name of the string. + * Save a pointer to the name. + */ + + switch (*cp) { + case ('\0'): + return(ROFF_CONT); + case ('('): + cp++; + maxl = 2; + break; + case ('['): + cp++; + maxl = 0; + break; + default: + maxl = 1; + break; + } + stnam = cp; + + /* Advance to the end of the name. */ + + for (i = 0; 0 == maxl || i < maxl; i++, cp++) { + if ('\0' == *cp) { + mandoc_msg + (MANDOCERR_BADESCAPE, + r->parse, ln, + (int)(stesc - *bufp), NULL); + return(ROFF_CONT); + } + if (0 == maxl && ']' == *cp) + break; + } + + /* + * Retrieve the replacement string; if it is + * undefined, resume searching for escapes. + */ + + res = roff_getstrn(r, stnam, (size_t)i); + + if (NULL == res) { + mandoc_msg + (MANDOCERR_BADESCAPE, r->parse, + ln, (int)(stesc - *bufp), NULL); + res = ""; + } + + /* Replace the escape sequence by the string. */ + + pos = stesc - *bufp; + + nsz = *szp + strlen(res) + 1; + n = mandoc_malloc(nsz); + + strlcpy(n, *bufp, (size_t)(stesc - *bufp + 1)); + strlcat(n, res, nsz); + strlcat(n, cp + (maxl ? 0 : 1), nsz); + + free(*bufp); + + *bufp = n; + *szp = nsz; + + if (EXPAND_LIMIT >= ++expand_count) + goto again; + + /* Just leave the string unexpanded. */ + mandoc_msg(MANDOCERR_ROFFLOOP, r->parse, ln, pos, NULL); + return(ROFF_IGN); + } + return(ROFF_CONT); +} + +/* + * Process text streams: convert all breakable hyphens into ASCII_HYPH. + */ +static enum rofferr +roff_parsetext(char *p) +{ + size_t sz; + const char *start; + enum mandoc_esc esc; + + start = p; + + while ('\0' != *p) { + sz = strcspn(p, "-\\"); + p += sz; + + if ('\0' == *p) + break; + + if ('\\' == *p) { + /* Skip over escapes. */ + p++; + esc = mandoc_escape + ((const char **)&p, NULL, NULL); + if (ESCAPE_ERROR == esc) + break; + continue; + } else if (p == start) { + p++; + continue; + } + + if (isalpha((unsigned char)p[-1]) && + isalpha((unsigned char)p[1])) + *p = ASCII_HYPH; + p++; + } + + return(ROFF_CONT); +} + +enum rofferr +roff_parseln(struct roff *r, int ln, char **bufp, + size_t *szp, int pos, int *offs) +{ + enum rofft t; + enum rofferr e; + int ppos, ctl; + + /* + * Run the reserved-word filter only if we have some reserved + * words to fill in. + */ + + e = roff_res(r, bufp, szp, ln, pos); + if (ROFF_IGN == e) + return(e); + assert(ROFF_CONT == e); + + ppos = pos; + ctl = mandoc_getcontrol(*bufp, &pos); + + /* + * First, if a scope is open and we're not a macro, pass the + * text through the macro's filter. If a scope isn't open and + * we're not a macro, just let it through. + * Finally, if there's an equation scope open, divert it into it + * no matter our state. + */ + + if (r->last && ! ctl) { + t = r->last->tok; + assert(roffs[t].text); + e = (*roffs[t].text) + (r, t, bufp, szp, ln, pos, pos, offs); + assert(ROFF_IGN == e || ROFF_CONT == e); + if (ROFF_CONT != e) + return(e); + if (r->eqn) + return(eqn_read(&r->eqn, ln, *bufp, pos, offs)); + if (r->tbl) + return(tbl_read(r->tbl, ln, *bufp, pos)); + return(roff_parsetext(*bufp + pos)); + } else if ( ! ctl) { + if (r->eqn) + return(eqn_read(&r->eqn, ln, *bufp, pos, offs)); + if (r->tbl) + return(tbl_read(r->tbl, ln, *bufp, pos)); + return(roff_parsetext(*bufp + pos)); + } else if (r->eqn) + return(eqn_read(&r->eqn, ln, *bufp, ppos, offs)); + + /* + * If a scope is open, go to the child handler for that macro, + * as it may want to preprocess before doing anything with it. + * Don't do so if an equation is open. + */ + + if (r->last) { + t = r->last->tok; + assert(roffs[t].sub); + return((*roffs[t].sub) + (r, t, bufp, szp, + ln, ppos, pos, offs)); + } + + /* + * Lastly, as we've no scope open, try to look up and execute + * the new macro. If no macro is found, simply return and let + * the compilers handle it. + */ + + if (ROFF_MAX == (t = roff_parse(r, *bufp, &pos))) + return(ROFF_CONT); + + assert(roffs[t].proc); + return((*roffs[t].proc) + (r, t, bufp, szp, + ln, ppos, pos, offs)); +} + + +void +roff_endparse(struct roff *r) +{ + + if (r->last) + mandoc_msg(MANDOCERR_SCOPEEXIT, r->parse, + r->last->line, r->last->col, NULL); + + if (r->eqn) { + mandoc_msg(MANDOCERR_SCOPEEXIT, r->parse, + r->eqn->eqn.ln, r->eqn->eqn.pos, NULL); + eqn_end(&r->eqn); + } + + if (r->tbl) { + mandoc_msg(MANDOCERR_SCOPEEXIT, r->parse, + r->tbl->line, r->tbl->pos, NULL); + tbl_end(&r->tbl); + } +} + +/* + * Parse a roff node's type from the input buffer. This must be in the + * form of ".foo xxx" in the usual way. + */ +static enum rofft +roff_parse(struct roff *r, const char *buf, int *pos) +{ + const char *mac; + size_t maclen; + enum rofft t; + + if ('\0' == buf[*pos] || '"' == buf[*pos] || + '\t' == buf[*pos] || ' ' == buf[*pos]) + return(ROFF_MAX); + + /* + * We stop the macro parse at an escape, tab, space, or nil. + * However, `\}' is also a valid macro, so make sure we don't + * clobber it by seeing the `\' as the end of token. + */ + + mac = buf + *pos; + maclen = strcspn(mac + 1, " \\\t\0") + 1; + + t = (r->current_string = roff_getstrn(r, mac, maclen)) + ? ROFF_USERDEF : roffhash_find(mac, maclen); + + *pos += (int)maclen; + + while (buf[*pos] && ' ' == buf[*pos]) + (*pos)++; + + return(t); +} + +/* ARGSUSED */ +static enum rofferr +roff_cblock(ROFF_ARGS) +{ + + /* + * A block-close `..' should only be invoked as a child of an + * ignore macro, otherwise raise a warning and just ignore it. + */ + + if (NULL == r->last) { + mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL); + return(ROFF_IGN); + } + + switch (r->last->tok) { + case (ROFF_am): + /* FALLTHROUGH */ + case (ROFF_ami): + /* FALLTHROUGH */ + case (ROFF_am1): + /* FALLTHROUGH */ + case (ROFF_de): + /* ROFF_de1 is remapped to ROFF_de in roff_block(). */ + /* FALLTHROUGH */ + case (ROFF_dei): + /* FALLTHROUGH */ + case (ROFF_ig): + break; + default: + mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL); + return(ROFF_IGN); + } + + if ((*bufp)[pos]) + mandoc_msg(MANDOCERR_ARGSLOST, r->parse, ln, pos, NULL); + + roffnode_pop(r); + roffnode_cleanscope(r); + return(ROFF_IGN); + +} + + +static void +roffnode_cleanscope(struct roff *r) +{ + + while (r->last) { + if (--r->last->endspan < 0) + break; + roffnode_pop(r); + } +} + + +/* ARGSUSED */ +static enum rofferr +roff_ccond(ROFF_ARGS) +{ + + if (NULL == r->last) { + mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL); + return(ROFF_IGN); + } + + switch (r->last->tok) { + case (ROFF_el): + /* FALLTHROUGH */ + case (ROFF_ie): + /* FALLTHROUGH */ + case (ROFF_if): + break; + default: + mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL); + return(ROFF_IGN); + } + + if (r->last->endspan > -1) { + mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL); + return(ROFF_IGN); + } + + if ((*bufp)[pos]) + mandoc_msg(MANDOCERR_ARGSLOST, r->parse, ln, pos, NULL); + + roffnode_pop(r); + roffnode_cleanscope(r); + return(ROFF_IGN); +} + + +/* ARGSUSED */ +static enum rofferr +roff_block(ROFF_ARGS) +{ + int sv; + size_t sz; + char *name; + + name = NULL; + + if (ROFF_ig != tok) { + if ('\0' == (*bufp)[pos]) { + mandoc_msg(MANDOCERR_NOARGS, r->parse, ln, ppos, NULL); + return(ROFF_IGN); + } + + /* + * Re-write `de1', since we don't really care about + * groff's strange compatibility mode, into `de'. + */ + + if (ROFF_de1 == tok) + tok = ROFF_de; + if (ROFF_de == tok) + name = *bufp + pos; + else + mandoc_msg(MANDOCERR_REQUEST, r->parse, ln, ppos, + roffs[tok].name); + + while ((*bufp)[pos] && ! isspace((unsigned char)(*bufp)[pos])) + pos++; + + while (isspace((unsigned char)(*bufp)[pos])) + (*bufp)[pos++] = '\0'; + } + + roffnode_push(r, tok, name, ln, ppos); + + /* + * At the beginning of a `de' macro, clear the existing string + * with the same name, if there is one. New content will be + * added from roff_block_text() in multiline mode. + */ + + if (ROFF_de == tok) + roff_setstr(r, name, "", 0); + + if ('\0' == (*bufp)[pos]) + return(ROFF_IGN); + + /* If present, process the custom end-of-line marker. */ + + sv = pos; + while ((*bufp)[pos] && ! isspace((unsigned char)(*bufp)[pos])) + pos++; + + /* + * Note: groff does NOT like escape characters in the input. + * Instead of detecting this, we're just going to let it fly and + * to hell with it. + */ + + assert(pos > sv); + sz = (size_t)(pos - sv); + + if (1 == sz && '.' == (*bufp)[sv]) + return(ROFF_IGN); + + r->last->end = mandoc_malloc(sz + 1); + + memcpy(r->last->end, *bufp + sv, sz); + r->last->end[(int)sz] = '\0'; + + if ((*bufp)[pos]) + mandoc_msg(MANDOCERR_ARGSLOST, r->parse, ln, pos, NULL); + + return(ROFF_IGN); +} + + +/* ARGSUSED */ +static enum rofferr +roff_block_sub(ROFF_ARGS) +{ + enum rofft t; + int i, j; + + /* + * First check whether a custom macro exists at this level. If + * it does, then check against it. This is some of groff's + * stranger behaviours. If we encountered a custom end-scope + * tag and that tag also happens to be a "real" macro, then we + * need to try interpreting it again as a real macro. If it's + * not, then return ignore. Else continue. + */ + + if (r->last->end) { + for (i = pos, j = 0; r->last->end[j]; j++, i++) + if ((*bufp)[i] != r->last->end[j]) + break; + + if ('\0' == r->last->end[j] && + ('\0' == (*bufp)[i] || + ' ' == (*bufp)[i] || + '\t' == (*bufp)[i])) { + roffnode_pop(r); + roffnode_cleanscope(r); + + while (' ' == (*bufp)[i] || '\t' == (*bufp)[i]) + i++; + + pos = i; + if (ROFF_MAX != roff_parse(r, *bufp, &pos)) + return(ROFF_RERUN); + return(ROFF_IGN); + } + } + + /* + * If we have no custom end-query or lookup failed, then try + * pulling it out of the hashtable. + */ + + t = roff_parse(r, *bufp, &pos); + + /* + * Macros other than block-end are only significant + * in `de' blocks; elsewhere, simply throw them away. + */ + if (ROFF_cblock != t) { + if (ROFF_de == tok) + roff_setstr(r, r->last->name, *bufp + ppos, 1); + return(ROFF_IGN); + } + + assert(roffs[t].proc); + return((*roffs[t].proc)(r, t, bufp, szp, + ln, ppos, pos, offs)); +} + + +/* ARGSUSED */ +static enum rofferr +roff_block_text(ROFF_ARGS) +{ + + if (ROFF_de == tok) + roff_setstr(r, r->last->name, *bufp + pos, 1); + + return(ROFF_IGN); +} + + +/* ARGSUSED */ +static enum rofferr +roff_cond_sub(ROFF_ARGS) +{ + enum rofft t; + enum roffrule rr; + char *ep; + + rr = r->last->rule; + roffnode_cleanscope(r); + + /* + * If the macro is unknown, first check if it contains a closing + * delimiter `\}'. If it does, close out our scope and return + * the currently-scoped rule (ignore or continue). Else, drop + * into the currently-scoped rule. + */ + + if (ROFF_MAX == (t = roff_parse(r, *bufp, &pos))) { + ep = &(*bufp)[pos]; + for ( ; NULL != (ep = strchr(ep, '\\')); ep++) { + ep++; + if ('}' != *ep) + continue; + + /* + * Make the \} go away. + * This is a little haphazard, as it's not quite + * clear how nroff does this. + * If we're at the end of line, then just chop + * off the \} and resize the buffer. + * If we aren't, then conver it to spaces. + */ + + if ('\0' == *(ep + 1)) { + *--ep = '\0'; + *szp -= 2; + } else + *(ep - 1) = *ep = ' '; + + roff_ccond(r, ROFF_ccond, bufp, szp, + ln, pos, pos + 2, offs); + break; + } + return(ROFFRULE_DENY == rr ? ROFF_IGN : ROFF_CONT); + } + + /* + * A denied conditional must evaluate its children if and only + * if they're either structurally required (such as loops and + * conditionals) or a closing macro. + */ + + if (ROFFRULE_DENY == rr) + if ( ! (ROFFMAC_STRUCT & roffs[t].flags)) + if (ROFF_ccond != t) + return(ROFF_IGN); + + assert(roffs[t].proc); + return((*roffs[t].proc)(r, t, bufp, szp, + ln, ppos, pos, offs)); +} + +/* ARGSUSED */ +static enum rofferr +roff_cond_text(ROFF_ARGS) +{ + char *ep; + enum roffrule rr; + + rr = r->last->rule; + roffnode_cleanscope(r); + + ep = &(*bufp)[pos]; + for ( ; NULL != (ep = strchr(ep, '\\')); ep++) { + ep++; + if ('}' != *ep) + continue; + *ep = '&'; + roff_ccond(r, ROFF_ccond, bufp, szp, + ln, pos, pos + 2, offs); + } + return(ROFFRULE_DENY == rr ? ROFF_IGN : ROFF_CONT); +} + +static enum roffrule +roff_evalcond(const char *v, int *pos) +{ + + switch (v[*pos]) { + case ('n'): + (*pos)++; + return(ROFFRULE_ALLOW); + case ('e'): + /* FALLTHROUGH */ + case ('o'): + /* FALLTHROUGH */ + case ('t'): + (*pos)++; + return(ROFFRULE_DENY); + default: + break; + } + + while (v[*pos] && ' ' != v[*pos]) + (*pos)++; + return(ROFFRULE_DENY); +} + +/* ARGSUSED */ +static enum rofferr +roff_line_ignore(ROFF_ARGS) +{ + + if (ROFF_it == tok) + mandoc_msg(MANDOCERR_REQUEST, r->parse, ln, ppos, "it"); + + return(ROFF_IGN); +} + +/* ARGSUSED */ +static enum rofferr +roff_cond(ROFF_ARGS) +{ + int sv; + enum roffrule rule; + + /* + * An `.el' has no conditional body: it will consume the value + * of the current rstack entry set in prior `ie' calls or + * defaults to DENY. + * + * If we're not an `el', however, then evaluate the conditional. + */ + + rule = ROFF_el == tok ? + (r->rstackpos < 0 ? + ROFFRULE_DENY : r->rstack[r->rstackpos--]) : + roff_evalcond(*bufp, &pos); + + sv = pos; + while (' ' == (*bufp)[pos]) + pos++; + + /* + * Roff is weird. If we have just white-space after the + * conditional, it's considered the BODY and we exit without + * really doing anything. Warn about this. It's probably + * wrong. + */ + + if ('\0' == (*bufp)[pos] && sv != pos) { + mandoc_msg(MANDOCERR_NOARGS, r->parse, ln, ppos, NULL); + return(ROFF_IGN); + } + + roffnode_push(r, tok, NULL, ln, ppos); + + r->last->rule = rule; + + /* + * An if-else will put the NEGATION of the current evaluated + * conditional into the stack of rules. + */ + + if (ROFF_ie == tok) { + if (r->rstackpos == RSTACK_MAX - 1) { + mandoc_msg(MANDOCERR_MEM, + r->parse, ln, ppos, NULL); + return(ROFF_ERR); + } + r->rstack[++r->rstackpos] = + ROFFRULE_DENY == r->last->rule ? + ROFFRULE_ALLOW : ROFFRULE_DENY; + } + + /* If the parent has false as its rule, then so do we. */ + + if (r->last->parent && ROFFRULE_DENY == r->last->parent->rule) + r->last->rule = ROFFRULE_DENY; + + /* + * Determine scope. If we're invoked with "\{" trailing the + * conditional, then we're in a multiline scope. Else our scope + * expires on the next line. + */ + + r->last->endspan = 1; + + if ('\\' == (*bufp)[pos] && '{' == (*bufp)[pos + 1]) { + r->last->endspan = -1; + pos += 2; + } + + /* + * If there are no arguments on the line, the next-line scope is + * assumed. + */ + + if ('\0' == (*bufp)[pos]) + return(ROFF_IGN); + + /* Otherwise re-run the roff parser after recalculating. */ + + *offs = pos; + return(ROFF_RERUN); +} + + +/* ARGSUSED */ +static enum rofferr +roff_ds(ROFF_ARGS) +{ + char *name, *string; + + /* + * A symbol is named by the first word following the macro + * invocation up to a space. Its value is anything after the + * name's trailing whitespace and optional double-quote. Thus, + * + * [.ds foo "bar " ] + * + * will have `bar " ' as its value. + */ + + string = *bufp + pos; + name = roff_getname(r, &string, ln, pos); + if ('\0' == *name) + return(ROFF_IGN); + + /* Read past initial double-quote. */ + if ('"' == *string) + string++; + + /* The rest is the value. */ + roff_setstr(r, name, string, 0); + return(ROFF_IGN); +} + +int +roff_regisset(const struct roff *r, enum regs reg) +{ + + return(r->regs[(int)reg].set); +} + +unsigned int +roff_regget(const struct roff *r, enum regs reg) +{ + + return(r->regs[(int)reg].u); +} + +void +roff_regunset(struct roff *r, enum regs reg) +{ + + r->regs[(int)reg].set = 0; +} + +/* ARGSUSED */ +static enum rofferr +roff_nr(ROFF_ARGS) +{ + const char *key; + char *val; + int iv; + + val = *bufp + pos; + key = roff_getname(r, &val, ln, pos); + + if (0 == strcmp(key, "nS")) { + r->regs[(int)REG_nS].set = 1; + if ((iv = mandoc_strntoi(val, strlen(val), 10)) >= 0) + r->regs[(int)REG_nS].u = (unsigned)iv; + else + r->regs[(int)REG_nS].u = 0u; + } + + return(ROFF_IGN); +} + +/* ARGSUSED */ +static enum rofferr +roff_rm(ROFF_ARGS) +{ + const char *name; + char *cp; + + cp = *bufp + pos; + while ('\0' != *cp) { + name = roff_getname(r, &cp, ln, (int)(cp - *bufp)); + if ('\0' != *name) + roff_setstr(r, name, NULL, 0); + } + return(ROFF_IGN); +} + +/* ARGSUSED */ +static enum rofferr +roff_TE(ROFF_ARGS) +{ + + if (NULL == r->tbl) + mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL); + else + tbl_end(&r->tbl); + + return(ROFF_IGN); +} + +/* ARGSUSED */ +static enum rofferr +roff_T_(ROFF_ARGS) +{ + + if (NULL == r->tbl) + mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL); + else + tbl_restart(ppos, ln, r->tbl); + + return(ROFF_IGN); +} + +#if 0 +static int +roff_closeeqn(struct roff *r) +{ + + return(r->eqn && ROFF_EQN == eqn_end(&r->eqn) ? 1 : 0); +} +#endif + +static void +roff_openeqn(struct roff *r, const char *name, int line, + int offs, const char *buf) +{ + struct eqn_node *e; + int poff; + + assert(NULL == r->eqn); + e = eqn_alloc(name, offs, line, r->parse); + + if (r->last_eqn) + r->last_eqn->next = e; + else + r->first_eqn = r->last_eqn = e; + + r->eqn = r->last_eqn = e; + + if (buf) { + poff = 0; + eqn_read(&r->eqn, line, buf, offs, &poff); + } +} + +/* ARGSUSED */ +static enum rofferr +roff_EQ(ROFF_ARGS) +{ + + roff_openeqn(r, *bufp + pos, ln, ppos, NULL); + return(ROFF_IGN); +} + +/* ARGSUSED */ +static enum rofferr +roff_EN(ROFF_ARGS) +{ + + mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL); + return(ROFF_IGN); +} + +/* ARGSUSED */ +static enum rofferr +roff_TS(ROFF_ARGS) +{ + struct tbl_node *t; + + if (r->tbl) { + mandoc_msg(MANDOCERR_SCOPEBROKEN, r->parse, ln, ppos, NULL); + tbl_end(&r->tbl); + } + + t = tbl_alloc(ppos, ln, r->parse); + + if (r->last_tbl) + r->last_tbl->next = t; + else + r->first_tbl = r->last_tbl = t; + + r->tbl = r->last_tbl = t; + return(ROFF_IGN); +} + +/* ARGSUSED */ +static enum rofferr +roff_tr(ROFF_ARGS) +{ + const char *p, *first, *second; + size_t fsz, ssz; + enum mandoc_esc esc; + + p = *bufp + pos; + + if ('\0' == *p) { + mandoc_msg(MANDOCERR_ARGCOUNT, r->parse, ln, ppos, NULL); + return(ROFF_IGN); + } + + while ('\0' != *p) { + fsz = ssz = 1; + + first = p++; + if ('\\' == *first) { + esc = mandoc_escape(&p, NULL, NULL); + if (ESCAPE_ERROR == esc) { + mandoc_msg + (MANDOCERR_BADESCAPE, r->parse, + ln, (int)(p - *bufp), NULL); + return(ROFF_IGN); + } + fsz = (size_t)(p - first); + } + + second = p++; + if ('\\' == *second) { + esc = mandoc_escape(&p, NULL, NULL); + if (ESCAPE_ERROR == esc) { + mandoc_msg + (MANDOCERR_BADESCAPE, r->parse, + ln, (int)(p - *bufp), NULL); + return(ROFF_IGN); + } + ssz = (size_t)(p - second); + } else if ('\0' == *second) { + mandoc_msg(MANDOCERR_ARGCOUNT, r->parse, + ln, (int)(p - *bufp), NULL); + second = " "; + p--; + } + + if (fsz > 1) { + roff_setstrn(&r->xmbtab, first, + fsz, second, ssz, 0); + continue; + } + + if (NULL == r->xtab) + r->xtab = mandoc_calloc + (128, sizeof(struct roffstr)); + + free(r->xtab[(int)*first].p); + r->xtab[(int)*first].p = mandoc_strndup(second, ssz); + r->xtab[(int)*first].sz = ssz; + } + + return(ROFF_IGN); +} + +/* ARGSUSED */ +static enum rofferr +roff_so(ROFF_ARGS) +{ + char *name; + + mandoc_msg(MANDOCERR_SO, r->parse, ln, ppos, NULL); + + /* + * Handle `so'. Be EXTREMELY careful, as we shouldn't be + * opening anything that's not in our cwd or anything beneath + * it. Thus, explicitly disallow traversing up the file-system + * or using absolute paths. + */ + + name = *bufp + pos; + if ('/' == *name || strstr(name, "../") || strstr(name, "/..")) { + mandoc_msg(MANDOCERR_SOPATH, r->parse, ln, pos, NULL); + return(ROFF_ERR); + } + + *offs = pos; + return(ROFF_SO); +} + +/* ARGSUSED */ +static enum rofferr +roff_userdef(ROFF_ARGS) +{ + const char *arg[9]; + char *cp, *n1, *n2; + int i; + + /* + * Collect pointers to macro argument strings + * and null-terminate them. + */ + cp = *bufp + pos; + for (i = 0; i < 9; i++) + arg[i] = '\0' == *cp ? "" : + mandoc_getarg(r->parse, &cp, ln, &pos); + + /* + * Expand macro arguments. + */ + *szp = 0; + n1 = cp = mandoc_strdup(r->current_string); + while (NULL != (cp = strstr(cp, "\\$"))) { + i = cp[2] - '1'; + if (0 > i || 8 < i) { + /* Not an argument invocation. */ + cp += 2; + continue; + } + + *szp = strlen(n1) - 3 + strlen(arg[i]) + 1; + n2 = mandoc_malloc(*szp); + + strlcpy(n2, n1, (size_t)(cp - n1 + 1)); + strlcat(n2, arg[i], *szp); + strlcat(n2, cp + 3, *szp); + + cp = n2 + (cp - n1); + free(n1); + n1 = n2; + } + + /* + * Replace the macro invocation + * by the expanded macro. + */ + free(*bufp); + *bufp = n1; + if (0 == *szp) + *szp = strlen(*bufp) + 1; + + return(*szp > 1 && '\n' == (*bufp)[(int)*szp - 2] ? + ROFF_REPARSE : ROFF_APPEND); +} + +static char * +roff_getname(struct roff *r, char **cpp, int ln, int pos) +{ + char *name, *cp; + + name = *cpp; + if ('\0' == *name) + return(name); + + /* Read until end of name. */ + for (cp = name; '\0' != *cp && ' ' != *cp; cp++) { + if ('\\' != *cp) + continue; + cp++; + if ('\\' == *cp) + continue; + mandoc_msg(MANDOCERR_NAMESC, r->parse, ln, pos, NULL); + *cp = '\0'; + name = cp; + } + + /* Nil-terminate name. */ + if ('\0' != *cp) + *(cp++) = '\0'; + + /* Read past spaces. */ + while (' ' == *cp) + cp++; + + *cpp = cp; + return(name); +} + +/* + * Store *string into the user-defined string called *name. + * In multiline mode, append to an existing entry and append '\n'; + * else replace the existing entry, if there is one. + * To clear an existing entry, call with (*r, *name, NULL, 0). + */ +static void +roff_setstr(struct roff *r, const char *name, const char *string, + int multiline) +{ + + roff_setstrn(&r->strtab, name, strlen(name), string, + string ? strlen(string) : 0, multiline); +} + +static void +roff_setstrn(struct roffkv **r, const char *name, size_t namesz, + const char *string, size_t stringsz, int multiline) +{ + struct roffkv *n; + char *c; + int i; + size_t oldch, newch; + + /* Search for an existing string with the same name. */ + n = *r; + + while (n && strcmp(name, n->key.p)) + n = n->next; + + if (NULL == n) { + /* Create a new string table entry. */ + n = mandoc_malloc(sizeof(struct roffkv)); + n->key.p = mandoc_strndup(name, namesz); + n->key.sz = namesz; + n->val.p = NULL; + n->val.sz = 0; + n->next = *r; + *r = n; + } else if (0 == multiline) { + /* In multiline mode, append; else replace. */ + free(n->val.p); + n->val.p = NULL; + n->val.sz = 0; + } + + if (NULL == string) + return; + + /* + * One additional byte for the '\n' in multiline mode, + * and one for the terminating '\0'. + */ + newch = stringsz + (multiline ? 2u : 1u); + + if (NULL == n->val.p) { + n->val.p = mandoc_malloc(newch); + *n->val.p = '\0'; + oldch = 0; + } else { + oldch = n->val.sz; + n->val.p = mandoc_realloc(n->val.p, oldch + newch); + } + + /* Skip existing content in the destination buffer. */ + c = n->val.p + (int)oldch; + + /* Append new content to the destination buffer. */ + i = 0; + while (i < (int)stringsz) { + /* + * Rudimentary roff copy mode: + * Handle escaped backslashes. + */ + if ('\\' == string[i] && '\\' == string[i + 1]) + i++; + *c++ = string[i++]; + } + + /* Append terminating bytes. */ + if (multiline) + *c++ = '\n'; + + *c = '\0'; + n->val.sz = (int)(c - n->val.p); +} + +static const char * +roff_getstrn(const struct roff *r, const char *name, size_t len) +{ + const struct roffkv *n; + + for (n = r->strtab; n; n = n->next) + if (0 == strncmp(name, n->key.p, len) && + '\0' == n->key.p[(int)len]) + return(n->val.p); + + return(NULL); +} + +static void +roff_freestr(struct roffkv *r) +{ + struct roffkv *n, *nn; + + for (n = r; n; n = nn) { + free(n->key.p); + free(n->val.p); + nn = n->next; + free(n); + } +} + +const struct tbl_span * +roff_span(const struct roff *r) +{ + + return(r->tbl ? tbl_span(r->tbl) : NULL); +} + +const struct eqn * +roff_eqn(const struct roff *r) +{ + + return(r->last_eqn ? &r->last_eqn->eqn : NULL); +} + +/* + * Duplicate an input string, making the appropriate character + * conversations (as stipulated by `tr') along the way. + * Returns a heap-allocated string with all the replacements made. + */ +char * +roff_strdup(const struct roff *r, const char *p) +{ + const struct roffkv *cp; + char *res; + const char *pp; + size_t ssz, sz; + enum mandoc_esc esc; + + if (NULL == r->xmbtab && NULL == r->xtab) + return(mandoc_strdup(p)); + else if ('\0' == *p) + return(mandoc_strdup("")); + + /* + * Step through each character looking for term matches + * (remember that a `tr' can be invoked with an escape, which is + * a glyph but the escape is multi-character). + * We only do this if the character hash has been initialised + * and the string is >0 length. + */ + + res = NULL; + ssz = 0; + + while ('\0' != *p) { + if ('\\' != *p && r->xtab && r->xtab[(int)*p].p) { + sz = r->xtab[(int)*p].sz; + res = mandoc_realloc(res, ssz + sz + 1); + memcpy(res + ssz, r->xtab[(int)*p].p, sz); + ssz += sz; + p++; + continue; + } else if ('\\' != *p) { + res = mandoc_realloc(res, ssz + 2); + res[ssz++] = *p++; + continue; + } + + /* Search for term matches. */ + for (cp = r->xmbtab; cp; cp = cp->next) + if (0 == strncmp(p, cp->key.p, cp->key.sz)) + break; + + if (NULL != cp) { + /* + * A match has been found. + * Append the match to the array and move + * forward by its keysize. + */ + res = mandoc_realloc + (res, ssz + cp->val.sz + 1); + memcpy(res + ssz, cp->val.p, cp->val.sz); + ssz += cp->val.sz; + p += (int)cp->key.sz; + continue; + } + + /* + * Handle escapes carefully: we need to copy + * over just the escape itself, or else we might + * do replacements within the escape itself. + * Make sure to pass along the bogus string. + */ + pp = p++; + esc = mandoc_escape(&p, NULL, NULL); + if (ESCAPE_ERROR == esc) { + sz = strlen(pp); + res = mandoc_realloc(res, ssz + sz + 1); + memcpy(res + ssz, pp, sz); + break; + } + /* + * We bail out on bad escapes. + * No need to warn: we already did so when + * roff_res() was called. + */ + sz = (int)(p - pp); + res = mandoc_realloc(res, ssz + sz + 1); + memcpy(res + ssz, pp, sz); + ssz += sz; + } + + res[(int)ssz] = '\0'; + return(res); +} @@ -0,0 +1,39 @@ +/* $Id: st.c,v 1.9 2011/03/22 14:33:05 kristaps Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "mdoc.h" +#include "mandoc.h" +#include "libmdoc.h" + +#define LINE(x, y) \ + if (0 == strcmp(p, x)) return(y); + +const char * +mdoc_a2st(const char *p) +{ + +#include "st.in" + + return(NULL); +} diff --git a/st.in b/st.in new file mode 100644 index 000000000000..3ba41dd359d3 --- /dev/null +++ b/st.in @@ -0,0 +1,78 @@ +/* $Id: st.in,v 1.19 2012/02/26 21:47:09 schwarze Exp $ */ +/* + * Copyright (c) 2009, 2010 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This file defines the .St macro arguments. If you add a new + * standard, make sure that the left-and side corresponds to the .St + * argument (like .St -p1003.1) and the right-hand side corresponds to + * the formatted output string. + * + * Be sure to escape strings. + * The non-breaking blanks prevent ending an output line right before + * a number. Groff prevent line breaks at the same places. + * + * REMEMBER TO ADD NEW STANDARDS TO MDOC.7! + */ + +LINE("-p1003.1-88", "IEEE Std 1003.1-1988 (\\(lqPOSIX.1\\(rq)") +LINE("-p1003.1-90", "IEEE Std 1003.1-1990 (\\(lqPOSIX.1\\(rq)") +LINE("-p1003.1-96", "ISO/IEC 9945-1:1996 (\\(lqPOSIX.1\\(rq)") +LINE("-p1003.1-2001", "IEEE Std 1003.1-2001 (\\(lqPOSIX.1\\(rq)") +LINE("-p1003.1-2004", "IEEE Std 1003.1-2004 (\\(lqPOSIX.1\\(rq)") +LINE("-p1003.1-2008", "IEEE Std 1003.1-2008 (\\(lqPOSIX.1\\(rq)") +LINE("-p1003.1", "IEEE Std 1003.1 (\\(lqPOSIX.1\\(rq)") +LINE("-p1003.1b", "IEEE Std 1003.1b (\\(lqPOSIX.1\\(rq)") +LINE("-p1003.1b-93", "IEEE Std 1003.1b-1993 (\\(lqPOSIX.1\\(rq)") +LINE("-p1003.1c-95", "IEEE Std 1003.1c-1995 (\\(lqPOSIX.1\\(rq)") +LINE("-p1003.1g-2000", "IEEE Std 1003.1g-2000 (\\(lqPOSIX.1\\(rq)") +LINE("-p1003.1i-95", "IEEE Std 1003.1i-1995 (\\(lqPOSIX.1\\(rq)") +LINE("-p1003.2-92", "IEEE Std 1003.2-1992 (\\(lqPOSIX.2\\(rq)") +LINE("-p1003.2a-92", "IEEE Std 1003.2a-1992 (\\(lqPOSIX.2\\(rq)") +LINE("-p1387.2-95", "IEEE Std 1387.2-1995 (\\(lqPOSIX.7.2\\(rq)") +LINE("-p1003.2", "IEEE Std 1003.2 (\\(lqPOSIX.2\\(rq)") +LINE("-p1387.2", "IEEE Std 1387.2 (\\(lqPOSIX.7.2\\(rq)") +LINE("-isoC", "ISO/IEC 9899:1990 (\\(lqISO\\~C90\\(rq)") +LINE("-isoC-90", "ISO/IEC 9899:1990 (\\(lqISO\\~C90\\(rq)") +LINE("-isoC-amd1", "ISO/IEC 9899/AMD1:1995 (\\(lqISO\\~C90, Amendment 1\\(rq)") +LINE("-isoC-tcor1", "ISO/IEC 9899/TCOR1:1994 (\\(lqISO\\~C90, Technical Corrigendum 1\\(rq)") +LINE("-isoC-tcor2", "ISO/IEC 9899/TCOR2:1995 (\\(lqISO\\~C90, Technical Corrigendum 2\\(rq)") +LINE("-isoC-99", "ISO/IEC 9899:1999 (\\(lqISO\\~C99\\(rq)") +LINE("-isoC-2011", "ISO/IEC 9899:2011 (\\(lqISO\\~C11\\(rq)") +LINE("-iso9945-1-90", "ISO/IEC 9945-1:1990 (\\(lqPOSIX.1\\(rq)") +LINE("-iso9945-1-96", "ISO/IEC 9945-1:1996 (\\(lqPOSIX.1\\(rq)") +LINE("-iso9945-2-93", "ISO/IEC 9945-2:1993 (\\(lqPOSIX.2\\(rq)") +LINE("-ansiC", "ANSI X3.159-1989 (\\(lqANSI\\~C89\\(rq)") +LINE("-ansiC-89", "ANSI X3.159-1989 (\\(lqANSI\\~C89\\(rq)") +LINE("-ansiC-99", "ANSI/ISO/IEC 9899-1999 (\\(lqANSI\\~C99\\(rq)") +LINE("-ieee754", "IEEE Std 754-1985") +LINE("-iso8802-3", "ISO 8802-3: 1989") +LINE("-iso8601", "ISO 8601") +LINE("-ieee1275-94", "IEEE Std 1275-1994 (\\(lqOpen Firmware\\(rq)") +LINE("-xpg3", "X/Open Portability Guide Issue\\~3 (\\(lqXPG3\\(rq)") +LINE("-xpg4", "X/Open Portability Guide Issue\\~4 (\\(lqXPG4\\(rq)") +LINE("-xpg4.2", "X/Open Portability Guide Issue\\~4, Version\\~2 (\\(lqXPG4.2\\(rq)") +LINE("-xpg4.3", "X/Open Portability Guide Issue\\~4, Version\\~3 (\\(lqXPG4.3\\(rq)") +LINE("-xbd5", "X/Open Base Definitions Issue\\~5 (\\(lqXBD5\\(rq)") +LINE("-xcu5", "X/Open Commands and Utilities Issue\\~5 (\\(lqXCU5\\(rq)") +LINE("-xsh5", "X/Open System Interfaces and Headers Issue\\~5 (\\(lqXSH5\\(rq)") +LINE("-xns5", "X/Open Networking Services Issue\\~5 (\\(lqXNS5\\(rq)") +LINE("-xns5.2", "X/Open Networking Services Issue\\~5.2 (\\(lqXNS5.2\\(rq)") +LINE("-xns5.2d2.0", "X/Open Networking Services Issue\\~5.2 Draft\\~2.0 (\\(lqXNS5.2D2.0\\(rq)") +LINE("-xcurses4.2", "X/Open Curses Issue\\~4, Version\\~2 (\\(lqXCURSES4.2\\(rq)") +LINE("-susv2", "Version\\~2 of the Single UNIX Specification") +LINE("-susv3", "Version\\~3 of the Single UNIX Specification") +LINE("-svid4", "System\\~V Interface Definition, Fourth Edition (\\(lqSVID4\\(rq)") diff --git a/style.css b/style.css new file mode 100644 index 000000000000..ee891f41ea92 --- /dev/null +++ b/style.css @@ -0,0 +1,144 @@ +/* $Id: style.css,v 1.25 2011/08/26 09:03:17 kristaps Exp $ */ + +/* + * This is an example style-sheet provided for mandoc(1) and the -Thtml + * or -Txhtml output mode. + * + * It mimics the appearance of the traditional cvsweb output. + * + * See mdoc(7) and man(7) for macro explanations. + */ + +html { max-width: 880px; margin-left: 1em; } +body { font-size: smaller; font-family: Helvetica,Arial,sans-serif; } +h1 { margin-bottom: 1ex; font-size: 110%; margin-left: -4ex; } /* Section header (Sh, SH). */ +h2 { margin-bottom: 1ex; font-size: 105%; margin-left: -2ex; } /* Sub-section header (Ss, SS). */ +table { width: 100%; margin-top: 0ex; margin-bottom: 0ex; } /* All tables. */ +td { vertical-align: top; } /* All table cells. */ +p { } /* Paragraph: Pp, Lp. */ +blockquote { margin-left: 5ex; margin-top: 0ex; margin-bottom: 0ex; } /* D1. */ +div.section { margin-bottom: 2ex; margin-left: 5ex; } /* Sections (Sh, SH). */ +div.subsection { } /* Sub-sections (Ss, SS). */ +table.synopsis { } /* SYNOPSIS section table. */ + +/* Preamble structure. */ + +table.foot { font-size: smaller; margin-top: 1em; border-top: 1px dotted #dddddd; } /* Document footer. */ +td.foot-date { width: 50%; } /* Document footer: date. */ +td.foot-os { width: 50%; text-align: right; } /* Document footer: OS/source. */ +table.head { font-size: smaller; margin-bottom: 1em; border-bottom: 1px dotted #dddddd; } /* Document header. */ +td.head-ltitle { width: 10%; } /* Document header: left-title. */ +td.head-vol { width: 80%; text-align: center; } /* Document header: volume. */ +td.head-rtitle { width: 10%; text-align: right; } /* Document header: right-title. */ + +/* General font modes. */ + +i { } /* Italic: BI, IB, I, (implicit). */ +.emph { font-style: italic; font-weight: normal; } /* Emphasis: Em, Bl -emphasis. */ +b { } /* Bold: SB, BI, IB, BR, RB, B, (implicit). */ +.symb { font-style: normal; font-weight: bold; } /* Symbolic: Sy, Ms, Bf -symbolic. */ +small { } /* Small: SB, SM. */ +.lit { font-style: normal; font-weight: normal; font-family: monospace; } /* Literal: Dl, Li, Ql, Bf -literal, Bl -literal, Bl -unfilled. */ + +/* Block modes. */ + +.display { } /* Top of all Bd, D1, Dl. */ +.list { } /* Top of all Bl. */ + +/* Context-specific modes. */ + +i.addr { font-weight: normal; } /* Address (Ad). */ +i.arg { font-weight: normal; } /* Command argument (Ar). */ +span.author { } /* Author name (An). */ +b.cmd { font-style: normal; } /* Command (Cm). */ +b.config { font-style: normal; } /* Config statement (Cd). */ +span.define { } /* Defines (Dv). */ +span.desc { } /* Nd. After em-dash. */ +b.diag { font-style: normal; } /* Diagnostic (Bl -diag). */ +span.env { } /* Environment variables (Ev). */ +span.errno { } /* Error string (Er). */ +i.farg { font-weight: normal; } /* Function argument (Fa, Fn). */ +i.file { font-weight: normal; } /* File (Pa). */ +b.flag { font-style: normal; } /* Flag (Fl, Cm). */ +b.fname { font-style: normal; } /* Function name (Fa, Fn, Rv). */ +i.ftype { font-weight: normal; } /* Function types (Ft, Fn). */ +b.includes { font-style: normal; } /* Header includes (In). */ +span.lib { } /* Library (Lb). */ +i.link-sec { font-weight: normal; } /* Section links (Sx). */ +b.macro { font-style: normal; } /* Macro-ish thing (Fd). */ +b.name { font-style: normal; } /* Name of utility (Nm). */ +span.opt { } /* Options (Op, Oo/Oc). */ +span.ref { } /* Citations (Rs). */ +span.ref-auth { } /* Reference author (%A). */ +i.ref-book { font-weight: normal; } /* Reference book (%B). */ +span.ref-city { } /* Reference city (%C). */ +span.ref-date { } /* Reference date (%D). */ +i.ref-issue { font-weight: normal; } /* Reference issuer/publisher (%I). */ +i.ref-jrnl { font-weight: normal; } /* Reference journal (%J). */ +span.ref-num { } /* Reference number (%N). */ +span.ref-opt { } /* Reference optionals (%O). */ +span.ref-page { } /* Reference page (%P). */ +span.ref-corp { } /* Reference corporate/foreign author (%Q). */ +span.ref-rep { } /* Reference report (%R). */ +span.ref-title { text-decoration: underline; } /* Reference title (%T). */ +span.ref-vol { } /* Reference volume (%V). */ +span.type { font-style: italic; font-weight: normal; } /* Variable types (Vt). */ +span.unix { } /* Unices (Ux, Ox, Nx, Fx, Bx, Bsx, Dx). */ +b.utility { font-style: normal; } /* Name of utility (Ex). */ +b.var { font-style: normal; } /* Variables (Rv). */ + +a.link-ext { } /* Off-site link (Lk). */ +a.link-includes { } /* Include-file link (In). */ +a.link-mail { } /* Mailto links (Mt). */ +a.link-man { } /* Manual links (Xr). */ +a.link-ref { } /* Reference section links (%Q). */ +a.link-sec { } /* Section links (Sx). */ + +/* Formatting for lists. See mdoc(7). */ + +dl.list-diag { } +dt.list-diag { } +dd.list-diag { } + +dl.list-hang { } +dt.list-hang { } +dd.list-hang { } + +dl.list-inset { } +dt.list-inset { } +dd.list-inset { } + +dl.list-ohang { } +dt.list-ohang { } +dd.list-ohang { margin-left: 0ex; } + +dl.list-tag { } +dt.list-tag { } +dd.list-tag { } + +table.list-col { } +tr.list-col { } +td.list-col { } + +ul.list-bul { list-style-type: disc; padding-left: 1em; } +li.list-bul { } + +ul.list-dash { list-style-type: none; padding-left: 0em; } +li.list-dash:before { content: "\2014 "; } + +ul.list-hyph { list-style-type: none; padding-left: 0em; } +li.list-hyph:before { content: "\2013 "; } + +ul.list-item { list-style-type: none; padding-left: 0em; } +li.list-item { } + +ol.list-enum { padding-left: 2em; } +li.list-enum { } + +/* Equation modes. See eqn(7). */ + +span.eqn { } + +/* Table modes. See tbl(7). */ + +table.tbl { } diff --git a/tbl.7 b/tbl.7 new file mode 100644 index 000000000000..ea3d2ba3a8b6 --- /dev/null +++ b/tbl.7 @@ -0,0 +1,348 @@ +.\" $Id: tbl.7,v 1.16 2011/09/03 00:29:21 kristaps Exp $ +.\" +.\" Copyright (c) 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: September 3 2011 $ +.Dt TBL 7 +.Os +.Sh NAME +.Nm tbl +.Nd tbl language reference for mandoc +.Sh DESCRIPTION +The +.Nm tbl +language is a table-formatting language. +It is used within +.Xr mdoc 7 +and +.Xr man 7 +.Ux +manual pages. +This manual describes the subset of the +.Nm +language accepted by the +.Xr mandoc 1 +utility. +.Pp +Tables within +.Xr mdoc 7 +or +.Xr man 7 +are enclosed by the +.Sq TS +and +.Sq TE +macro tags, whose precise syntax is documented in +.Xr roff 7 . +Tables consist of a series of options on a single line, followed by the +table layout, followed by data. +.Pp +For example, the following creates a boxed table with digits centred in +the cells. +.Bd -literal -offset indent +\&.TS +tab(:) box; +c5 c5 c5. +1:2:3 +4:5:6 +\&.TE +.Ed +.Pp +When formatted, the following output is produced: +.Bd -filled -offset indent -compact +.TS +tab(:) box; +c5 c5 c5. +1:2:3 +4:5:6 +.TE +.Ed +.Pp +The +.Nm +implementation in +.Xr mandoc 1 +is +.Ud +.Sh TABLE STRUCTURE +Tables are enclosed by the +.Sq TS +and +.Sq TE +.Xr roff 7 +macros. +A table consists of an optional single line of table +.Sx Options +terminated by a semicolon, followed by one or more lines of +.Sx Layout +specifications terminated by a period, then +.Sx Data . +All input must be 7-bit ASCII. +Example: +.Bd -literal -offset indent +\&.TS +box tab(:); +c | c +| c | c. +1:2 +3:4 +\&.TE +.Ed +.Pp +Table data is +.Em pre-processed , +that is, data rows are parsed then inserted into the underlying stream +of input data. +This allows data rows to be interspersed by arbitrary +.Xr roff 7 , +.Xr mdoc 7 , +and +.Xr man 7 +macros such as +.Bd -literal -offset indent +\&.TS +tab(:); +c c c. +1:2:3 +\&.Ao +3:2:1 +\&.Ac +\&.TE +.Ed +.Pp +in the case of +.Xr mdoc 7 +or +.Bd -literal -offset indent +\&.TS +tab(:); +c c c. +\&.ds ab 2 +1:\e*(ab:3 +\&.I +3:2:1 +\&.TE +.Ed +.Pp +in the case of +.Xr man 7 . +.Ss Options +The first line of a table consists of space-separated option keys and +modifiers terminated by a semicolon. +If the first line does not have a terminating semicolon, it is assumed +that no options are specified and instead a +.Sx Layout +is processed. +Some options accept arguments enclosed by parenthesis. +The following case-insensitive options are available: +.Bl -tag -width Ds +.It Cm center +This option is not supported by +.Xr mandoc 1 . +This may also be invoked with +.Cm centre . +.It Cm delim +Accepts a two-character argument. +This option is not supported by +.Xr mandoc 1 . +.It Cm expand +This option is not supported by +.Xr mandoc 1 . +.It Cm box +Draw a single-line box around the table. +This may also be invoked with +.Cm frame . +.It Cm doublebox +Draw a double-line box around the table. +This may also be invoked with +.Cm doubleframe . +.It Cm allbox +This option is not supported by +.Xr mandoc 1 . +.It Cm tab +Accepts a single-character argument. +This character is used as a delimiter between data cells, which otherwise +defaults to the tab character. +.It Cm linesize +Accepts a natural number (all digits). +This option is not supported by +.Xr mandoc 1 . +.It Cm nokeep +This option is not supported by +.Xr mandoc 1 . +.It Cm decimalpoint +Accepts a single-character argument. +This character will be used as the decimal point with the +.Cm n +layout key. +.It Cm nospaces +This option is not supported by +.Xr mandoc 1 . +.El +.Ss Layout +The table layout follows +.Sx Options +or a +.Sq \&T& +macro invocation. +Layout specifies how data rows are displayed on output. +Each layout line corresponds to a line of data; the last layout line +applies to all remaining data lines. +Layout lines may also be separated by a comma. +Each layout cell consists of one of the following case-insensitive keys: +.Bl -tag -width Ds +.It Cm c +Centre a literal string within its column. +.It Cm r +Right-justify a literal string within its column. +.It Cm l +Left-justify a literal string within its column. +.It Cm n +Justify a number around its last decimal point. +If the decimal point is not found on the number, it's assumed to trail +the number. +.It Cm s +Horizontally span columns from the last +.No non- Ns Cm s +data cell. +It is an error if spanning columns follow a +.Cm \- +or +.Cm \(ba +cell, or come first. +This option is not supported by +.Xr mandoc 1 . +.It Cm a +Left-justify a literal string and pad with one space. +.It Cm ^ +Vertically span rows from the last +.No non- Ns Cm ^ +data cell. +It is an error to invoke a vertical span on the first layout row. +Unlike a horizontal spanner, you must specify an empty cell (if it not +empty, the data is discarded) in the corresponding data cell. +.It Cm \- +Replace the data cell (its contents will be lost) with a single +horizontal line. +This may also be invoked with +.Cm _ . +.It Cm = +Replace the data cell (its contents will be lost) with a double +horizontal line. +.It Cm \(ba +Emit a vertical bar instead of data. +.It Cm \(ba\(ba +Emit a double-vertical bar instead of data. +.El +.Pp +Keys may be followed by a set of modifiers. +A modifier is either a modifier key or a natural number for specifying +the minimum width of a column. +The following case-insensitive modifier keys are available: +.Cm z , +.Cm u , +.Cm e , +.Cm t , +.Cm d , +.Cm b , +.Cm i , +.Cm r , +and +.Cm f +.Po +followed by +.Cm b , +.Cm i , +.Cm r , +.Cm 3 , +.Cm 2 , +or +.Cm 1 +.Pc . +All of these are ignored by +.Xr mandoc 1 . +.Pp +For example, the following layout specifies a centre-justified column of +minimum width 10, followed by vertical bar, followed by a left-justified +column of minimum width 10, another vertical bar, then a column +justified about the decimal point in numbers: +.Pp +.Dl c10 | l10 | n +.Ss Data +The data section follows the last layout row. +By default, cells in a data section are delimited by a tab. +This behaviour may be changed with the +.Cm tab +option. +If +.Cm _ +or +.Cm = +is specified, a single or double line, respectively, is drawn across the +data field. +If +.Cm \e- +or +.Cm \e= +is specified, a line is drawn within the data field (i.e. terminating +within the cell and not draw to the border). +If the last cell of a line is +.Cm T{ , +all subsequent lines are included as part of the cell until +.Cm T} +is specified as its own data cell. +It may then be followed by a tab +.Pq or as designated by Cm tab +or an end-of-line to terminate the row. +.Sh COMPATIBILITY +This section documents compatibility between mandoc and other +.Nm +implementations, at this time limited to GNU tbl. +.Pp +.Bl -dash -compact +.It +In GNU tbl, comments and macros are disallowed prior to the data block +of a table. +The +.Xr mandoc 1 +implementation allows them. +.El +.Sh SEE ALSO +.Xr mandoc 1 , +.Xr man 7 , +.Xr mandoc_char 7 , +.Xr mdoc 7 , +.Xr roff 7 +.Rs +.%A M. E. Lesk +.%T Tbl\(emA Program to Format Tables +.%D June 11, 1976 +.Re +.Sh HISTORY +The tbl utility, a preprocessor for troff, was originally written by M. +E. Lesk at Bell Labs in 1975. +The GNU reimplementation of tbl, part of the groff package, was released +in 1990 by James Clark. +A standalone tbl implementation was written by Kristaps Dzonsons in +2010. +This formed the basis of the implementation that is part of the +.Xr mandoc 1 +utility. +.Sh AUTHORS +This +.Nm +reference was written by +.An Kristaps Dzonsons , +.Mt kristaps@bsd.lv . diff --git a/tbl.c b/tbl.c new file mode 100644 index 000000000000..b3d651be07ad --- /dev/null +++ b/tbl.c @@ -0,0 +1,175 @@ +/* $Id: tbl.c,v 1.26 2011/07/25 15:37:00 kristaps Exp $ */ +/* + * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "mandoc.h" +#include "libmandoc.h" +#include "libroff.h" + +enum rofferr +tbl_read(struct tbl_node *tbl, int ln, const char *p, int offs) +{ + int len; + const char *cp; + + cp = &p[offs]; + len = (int)strlen(cp); + + /* + * If we're in the options section and we don't have a + * terminating semicolon, assume we've moved directly into the + * layout section. No need to report a warning: this is, + * apparently, standard behaviour. + */ + + if (TBL_PART_OPTS == tbl->part && len) + if (';' != cp[len - 1]) + tbl->part = TBL_PART_LAYOUT; + + /* Now process each logical section of the table. */ + + switch (tbl->part) { + case (TBL_PART_OPTS): + return(tbl_option(tbl, ln, p) ? ROFF_IGN : ROFF_ERR); + case (TBL_PART_LAYOUT): + return(tbl_layout(tbl, ln, p) ? ROFF_IGN : ROFF_ERR); + case (TBL_PART_CDATA): + return(tbl_cdata(tbl, ln, p) ? ROFF_TBL : ROFF_IGN); + default: + break; + } + + /* + * This only returns zero if the line is empty, so we ignore it + * and continue on. + */ + return(tbl_data(tbl, ln, p) ? ROFF_TBL : ROFF_IGN); +} + +struct tbl_node * +tbl_alloc(int pos, int line, struct mparse *parse) +{ + struct tbl_node *p; + + p = mandoc_calloc(1, sizeof(struct tbl_node)); + p->line = line; + p->pos = pos; + p->parse = parse; + p->part = TBL_PART_OPTS; + p->opts.tab = '\t'; + p->opts.linesize = 12; + p->opts.decimal = '.'; + return(p); +} + +void +tbl_free(struct tbl_node *p) +{ + struct tbl_row *rp; + struct tbl_cell *cp; + struct tbl_span *sp; + struct tbl_dat *dp; + struct tbl_head *hp; + + while (NULL != (rp = p->first_row)) { + p->first_row = rp->next; + while (rp->first) { + cp = rp->first; + rp->first = cp->next; + free(cp); + } + free(rp); + } + + while (NULL != (sp = p->first_span)) { + p->first_span = sp->next; + while (sp->first) { + dp = sp->first; + sp->first = dp->next; + if (dp->string) + free(dp->string); + free(dp); + } + free(sp); + } + + while (NULL != (hp = p->first_head)) { + p->first_head = hp->next; + free(hp); + } + + free(p); +} + +void +tbl_restart(int line, int pos, struct tbl_node *tbl) +{ + if (TBL_PART_CDATA == tbl->part) + mandoc_msg(MANDOCERR_TBLBLOCK, tbl->parse, + tbl->line, tbl->pos, NULL); + + tbl->part = TBL_PART_LAYOUT; + tbl->line = line; + tbl->pos = pos; + + if (NULL == tbl->first_span || NULL == tbl->first_span->first) + mandoc_msg(MANDOCERR_TBLNODATA, tbl->parse, + tbl->line, tbl->pos, NULL); +} + +const struct tbl_span * +tbl_span(struct tbl_node *tbl) +{ + struct tbl_span *span; + + assert(tbl); + span = tbl->current_span ? tbl->current_span->next + : tbl->first_span; + if (span) + tbl->current_span = span; + return(span); +} + +void +tbl_end(struct tbl_node **tblp) +{ + struct tbl_node *tbl; + + tbl = *tblp; + *tblp = NULL; + + if (NULL == tbl->first_span || NULL == tbl->first_span->first) + mandoc_msg(MANDOCERR_TBLNODATA, tbl->parse, + tbl->line, tbl->pos, NULL); + + if (tbl->last_span) + tbl->last_span->flags |= TBL_SPAN_LAST; + + if (TBL_PART_CDATA == tbl->part) + mandoc_msg(MANDOCERR_TBLBLOCK, tbl->parse, + tbl->line, tbl->pos, NULL); +} + diff --git a/tbl_data.c b/tbl_data.c new file mode 100644 index 000000000000..129695d8bb96 --- /dev/null +++ b/tbl_data.c @@ -0,0 +1,276 @@ +/* $Id: tbl_data.c,v 1.24 2011/03/20 16:02:05 kristaps Exp $ */ +/* + * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <assert.h> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "mandoc.h" +#include "libmandoc.h" +#include "libroff.h" + +static int data(struct tbl_node *, struct tbl_span *, + int, const char *, int *); +static struct tbl_span *newspan(struct tbl_node *, int, + struct tbl_row *); + +static int +data(struct tbl_node *tbl, struct tbl_span *dp, + int ln, const char *p, int *pos) +{ + struct tbl_dat *dat; + struct tbl_cell *cp; + int sv, spans; + + cp = NULL; + if (dp->last && dp->last->layout) + cp = dp->last->layout->next; + else if (NULL == dp->last) + cp = dp->layout->first; + + /* + * Skip over spanners and vertical lines to data formats, since + * we want to match data with data layout cells in the header. + */ + + while (cp && (TBL_CELL_VERT == cp->pos || + TBL_CELL_DVERT == cp->pos || + TBL_CELL_SPAN == cp->pos)) + cp = cp->next; + + /* + * Stop processing when we reach the end of the available layout + * cells. This means that we have extra input. + */ + + if (NULL == cp) { + mandoc_msg(MANDOCERR_TBLEXTRADAT, + tbl->parse, ln, *pos, NULL); + /* Skip to the end... */ + while (p[*pos]) + (*pos)++; + return(1); + } + + dat = mandoc_calloc(1, sizeof(struct tbl_dat)); + dat->layout = cp; + dat->pos = TBL_DATA_NONE; + + assert(TBL_CELL_SPAN != cp->pos); + + for (spans = 0, cp = cp->next; cp; cp = cp->next) + if (TBL_CELL_SPAN == cp->pos) + spans++; + else + break; + + dat->spans = spans; + + if (dp->last) { + dp->last->next = dat; + dp->last = dat; + } else + dp->last = dp->first = dat; + + sv = *pos; + while (p[*pos] && p[*pos] != tbl->opts.tab) + (*pos)++; + + /* + * Check for a continued-data scope opening. This consists of a + * trailing `T{' at the end of the line. Subsequent lines, + * until a standalone `T}', are included in our cell. + */ + + if (*pos - sv == 2 && 'T' == p[sv] && '{' == p[sv + 1]) { + tbl->part = TBL_PART_CDATA; + return(0); + } + + assert(*pos - sv >= 0); + + dat->string = mandoc_malloc((size_t)(*pos - sv + 1)); + memcpy(dat->string, &p[sv], (size_t)(*pos - sv)); + dat->string[*pos - sv] = '\0'; + + if (p[*pos]) + (*pos)++; + + if ( ! strcmp(dat->string, "_")) + dat->pos = TBL_DATA_HORIZ; + else if ( ! strcmp(dat->string, "=")) + dat->pos = TBL_DATA_DHORIZ; + else if ( ! strcmp(dat->string, "\\_")) + dat->pos = TBL_DATA_NHORIZ; + else if ( ! strcmp(dat->string, "\\=")) + dat->pos = TBL_DATA_NDHORIZ; + else + dat->pos = TBL_DATA_DATA; + + if (TBL_CELL_HORIZ == dat->layout->pos || + TBL_CELL_DHORIZ == dat->layout->pos || + TBL_CELL_DOWN == dat->layout->pos) + if (TBL_DATA_DATA == dat->pos && '\0' != *dat->string) + mandoc_msg(MANDOCERR_TBLIGNDATA, + tbl->parse, ln, sv, NULL); + + return(1); +} + +/* ARGSUSED */ +int +tbl_cdata(struct tbl_node *tbl, int ln, const char *p) +{ + struct tbl_dat *dat; + size_t sz; + int pos; + + pos = 0; + + dat = tbl->last_span->last; + + if (p[pos] == 'T' && p[pos + 1] == '}') { + pos += 2; + if (p[pos] == tbl->opts.tab) { + tbl->part = TBL_PART_DATA; + pos++; + return(data(tbl, tbl->last_span, ln, p, &pos)); + } else if ('\0' == p[pos]) { + tbl->part = TBL_PART_DATA; + return(1); + } + + /* Fallthrough: T} is part of a word. */ + } + + dat->pos = TBL_DATA_DATA; + + if (dat->string) { + sz = strlen(p) + strlen(dat->string) + 2; + dat->string = mandoc_realloc(dat->string, sz); + strlcat(dat->string, " ", sz); + strlcat(dat->string, p, sz); + } else + dat->string = mandoc_strdup(p); + + if (TBL_CELL_DOWN == dat->layout->pos) + mandoc_msg(MANDOCERR_TBLIGNDATA, + tbl->parse, ln, pos, NULL); + + return(0); +} + +static struct tbl_span * +newspan(struct tbl_node *tbl, int line, struct tbl_row *rp) +{ + struct tbl_span *dp; + + dp = mandoc_calloc(1, sizeof(struct tbl_span)); + dp->line = line; + dp->tbl = &tbl->opts; + dp->layout = rp; + dp->head = tbl->first_head; + + if (tbl->last_span) { + tbl->last_span->next = dp; + tbl->last_span = dp; + } else { + tbl->last_span = tbl->first_span = dp; + tbl->current_span = NULL; + dp->flags |= TBL_SPAN_FIRST; + } + + return(dp); +} + +int +tbl_data(struct tbl_node *tbl, int ln, const char *p) +{ + struct tbl_span *dp; + struct tbl_row *rp; + int pos; + + pos = 0; + + if ('\0' == p[pos]) { + mandoc_msg(MANDOCERR_TBL, tbl->parse, ln, pos, NULL); + return(0); + } + + /* + * Choose a layout row: take the one following the last parsed + * span's. If that doesn't exist, use the last parsed span's. + * If there's no last parsed span, use the first row. Lastly, + * if the last span was a horizontal line, use the same layout + * (it doesn't "consume" the layout). + */ + + if (tbl->last_span) { + assert(tbl->last_span->layout); + if (tbl->last_span->pos == TBL_SPAN_DATA) { + for (rp = tbl->last_span->layout->next; + rp && rp->first; rp = rp->next) { + switch (rp->first->pos) { + case (TBL_CELL_HORIZ): + dp = newspan(tbl, ln, rp); + dp->pos = TBL_SPAN_HORIZ; + continue; + case (TBL_CELL_DHORIZ): + dp = newspan(tbl, ln, rp); + dp->pos = TBL_SPAN_DHORIZ; + continue; + default: + break; + } + break; + } + } else + rp = tbl->last_span->layout; + + if (NULL == rp) + rp = tbl->last_span->layout; + } else + rp = tbl->first_row; + + assert(rp); + + dp = newspan(tbl, ln, rp); + + if ( ! strcmp(p, "_")) { + dp->pos = TBL_SPAN_HORIZ; + return(1); + } else if ( ! strcmp(p, "=")) { + dp->pos = TBL_SPAN_DHORIZ; + return(1); + } + + dp->pos = TBL_SPAN_DATA; + + /* This returns 0 when TBL_PART_CDATA is entered. */ + + while ('\0' != p[pos]) + if ( ! data(tbl, dp, ln, p, &pos)) + return(0); + + return(1); +} diff --git a/tbl_html.c b/tbl_html.c new file mode 100644 index 000000000000..8e7dc05de0db --- /dev/null +++ b/tbl_html.c @@ -0,0 +1,151 @@ +/* $Id: tbl_html.c,v 1.9 2011/09/18 14:14:15 schwarze Exp $ */ +/* + * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "mandoc.h" +#include "out.h" +#include "html.h" + +static void html_tblopen(struct html *, const struct tbl_span *); +static size_t html_tbl_len(size_t, void *); +static size_t html_tbl_strlen(const char *, void *); + +/* ARGSUSED */ +static size_t +html_tbl_len(size_t sz, void *arg) +{ + + return(sz); +} + +/* ARGSUSED */ +static size_t +html_tbl_strlen(const char *p, void *arg) +{ + + return(strlen(p)); +} + +static void +html_tblopen(struct html *h, const struct tbl_span *sp) +{ + const struct tbl_head *hp; + struct htmlpair tag; + struct roffsu su; + struct roffcol *col; + + if (TBL_SPAN_FIRST & sp->flags) { + h->tbl.len = html_tbl_len; + h->tbl.slen = html_tbl_strlen; + tblcalc(&h->tbl, sp); + } + + assert(NULL == h->tblt); + PAIR_CLASS_INIT(&tag, "tbl"); + h->tblt = print_otag(h, TAG_TABLE, 1, &tag); + + for (hp = sp->head; hp; hp = hp->next) { + bufinit(h); + col = &h->tbl.cols[hp->ident]; + SCALE_HS_INIT(&su, col->width); + bufcat_su(h, "width", &su); + PAIR_STYLE_INIT(&tag, h); + print_otag(h, TAG_COL, 1, &tag); + } + + print_otag(h, TAG_TBODY, 0, NULL); +} + +void +print_tblclose(struct html *h) +{ + + assert(h->tblt); + print_tagq(h, h->tblt); + h->tblt = NULL; +} + +void +print_tbl(struct html *h, const struct tbl_span *sp) +{ + const struct tbl_head *hp; + const struct tbl_dat *dp; + struct htmlpair tag; + struct tag *tt; + + /* Inhibit printing of spaces: we do padding ourselves. */ + + if (NULL == h->tblt) + html_tblopen(h, sp); + + assert(h->tblt); + + h->flags |= HTML_NONOSPACE; + h->flags |= HTML_NOSPACE; + + tt = print_otag(h, TAG_TR, 0, NULL); + + switch (sp->pos) { + case (TBL_SPAN_HORIZ): + /* FALLTHROUGH */ + case (TBL_SPAN_DHORIZ): + PAIR_INIT(&tag, ATTR_COLSPAN, "0"); + print_otag(h, TAG_TD, 1, &tag); + break; + default: + dp = sp->first; + for (hp = sp->head; hp; hp = hp->next) { + print_stagq(h, tt); + print_otag(h, TAG_TD, 0, NULL); + + switch (hp->pos) { + case (TBL_HEAD_VERT): + /* FALLTHROUGH */ + case (TBL_HEAD_DVERT): + continue; + case (TBL_HEAD_DATA): + if (NULL == dp) + break; + if (TBL_CELL_DOWN != dp->layout->pos) + if (dp->string) + print_text(h, dp->string); + dp = dp->next; + break; + } + } + break; + } + + print_tagq(h, tt); + + h->flags &= ~HTML_NONOSPACE; + + if (TBL_SPAN_LAST & sp->flags) { + assert(h->tbl.cols); + free(h->tbl.cols); + h->tbl.cols = NULL; + print_tblclose(h); + } + +} diff --git a/tbl_layout.c b/tbl_layout.c new file mode 100644 index 000000000000..7601f146cad6 --- /dev/null +++ b/tbl_layout.c @@ -0,0 +1,472 @@ +/* $Id: tbl_layout.c,v 1.22 2011/09/18 14:14:15 schwarze Exp $ */ +/* + * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <assert.h> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "mandoc.h" +#include "libmandoc.h" +#include "libroff.h" + +struct tbl_phrase { + char name; + enum tbl_cellt key; +}; + +/* + * FIXME: we can make this parse a lot nicer by, when an error is + * encountered in a layout key, bailing to the next key (i.e. to the + * next whitespace then continuing). + */ + +#define KEYS_MAX 11 + +static const struct tbl_phrase keys[KEYS_MAX] = { + { 'c', TBL_CELL_CENTRE }, + { 'r', TBL_CELL_RIGHT }, + { 'l', TBL_CELL_LEFT }, + { 'n', TBL_CELL_NUMBER }, + { 's', TBL_CELL_SPAN }, + { 'a', TBL_CELL_LONG }, + { '^', TBL_CELL_DOWN }, + { '-', TBL_CELL_HORIZ }, + { '_', TBL_CELL_HORIZ }, + { '=', TBL_CELL_DHORIZ }, + { '|', TBL_CELL_VERT } +}; + +static int mods(struct tbl_node *, struct tbl_cell *, + int, const char *, int *); +static int cell(struct tbl_node *, struct tbl_row *, + int, const char *, int *); +static void row(struct tbl_node *, int, const char *, int *); +static struct tbl_cell *cell_alloc(struct tbl_node *, + struct tbl_row *, enum tbl_cellt); +static void head_adjust(const struct tbl_cell *, + struct tbl_head *); + +static int +mods(struct tbl_node *tbl, struct tbl_cell *cp, + int ln, const char *p, int *pos) +{ + char buf[5]; + int i; + + /* Not all types accept modifiers. */ + + switch (cp->pos) { + case (TBL_CELL_DOWN): + /* FALLTHROUGH */ + case (TBL_CELL_HORIZ): + /* FALLTHROUGH */ + case (TBL_CELL_DHORIZ): + /* FALLTHROUGH */ + case (TBL_CELL_VERT): + /* FALLTHROUGH */ + case (TBL_CELL_DVERT): + return(1); + default: + break; + } + +mod: + /* + * XXX: since, at least for now, modifiers are non-conflicting + * (are separable by value, regardless of position), we let + * modifiers come in any order. The existing tbl doesn't let + * this happen. + */ + switch (p[*pos]) { + case ('\0'): + /* FALLTHROUGH */ + case (' '): + /* FALLTHROUGH */ + case ('\t'): + /* FALLTHROUGH */ + case (','): + /* FALLTHROUGH */ + case ('.'): + return(1); + default: + break; + } + + /* Throw away parenthesised expression. */ + + if ('(' == p[*pos]) { + (*pos)++; + while (p[*pos] && ')' != p[*pos]) + (*pos)++; + if (')' == p[*pos]) { + (*pos)++; + goto mod; + } + mandoc_msg(MANDOCERR_TBLLAYOUT, + tbl->parse, ln, *pos, NULL); + return(0); + } + + /* Parse numerical spacing from modifier string. */ + + if (isdigit((unsigned char)p[*pos])) { + for (i = 0; i < 4; i++) { + if ( ! isdigit((unsigned char)p[*pos + i])) + break; + buf[i] = p[*pos + i]; + } + buf[i] = '\0'; + + /* No greater than 4 digits. */ + + if (4 == i) { + mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, + ln, *pos, NULL); + return(0); + } + + *pos += i; + cp->spacing = (size_t)atoi(buf); + + goto mod; + /* NOTREACHED */ + } + + /* TODO: GNU has many more extensions. */ + + switch (tolower((unsigned char)p[(*pos)++])) { + case ('z'): + cp->flags |= TBL_CELL_WIGN; + goto mod; + case ('u'): + cp->flags |= TBL_CELL_UP; + goto mod; + case ('e'): + cp->flags |= TBL_CELL_EQUAL; + goto mod; + case ('t'): + cp->flags |= TBL_CELL_TALIGN; + goto mod; + case ('d'): + cp->flags |= TBL_CELL_BALIGN; + goto mod; + case ('w'): /* XXX for now, ignore minimal column width */ + goto mod; + case ('f'): + break; + case ('r'): + /* FALLTHROUGH */ + case ('b'): + /* FALLTHROUGH */ + case ('i'): + (*pos)--; + break; + default: + mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, + ln, *pos - 1, NULL); + return(0); + } + + switch (tolower((unsigned char)p[(*pos)++])) { + case ('3'): + /* FALLTHROUGH */ + case ('b'): + cp->flags |= TBL_CELL_BOLD; + goto mod; + case ('2'): + /* FALLTHROUGH */ + case ('i'): + cp->flags |= TBL_CELL_ITALIC; + goto mod; + case ('1'): + /* FALLTHROUGH */ + case ('r'): + goto mod; + default: + break; + } + + mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, + ln, *pos - 1, NULL); + return(0); +} + +static int +cell(struct tbl_node *tbl, struct tbl_row *rp, + int ln, const char *p, int *pos) +{ + int i; + enum tbl_cellt c; + + /* Parse the column position (`r', `R', `|', ...). */ + + for (i = 0; i < KEYS_MAX; i++) + if (tolower((unsigned char)p[*pos]) == keys[i].name) + break; + + if (KEYS_MAX == i) { + mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, + ln, *pos, NULL); + return(0); + } + + c = keys[i].key; + + /* + * If a span cell is found first, raise a warning and abort the + * parse. If a span cell is found and the last layout element + * isn't a "normal" layout, bail. + * + * FIXME: recover from this somehow? + */ + + if (TBL_CELL_SPAN == c) { + if (NULL == rp->first) { + mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, + ln, *pos, NULL); + return(0); + } else if (rp->last) + switch (rp->last->pos) { + case (TBL_CELL_VERT): + case (TBL_CELL_DVERT): + case (TBL_CELL_HORIZ): + case (TBL_CELL_DHORIZ): + mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, + ln, *pos, NULL); + return(0); + default: + break; + } + } + + /* + * If a vertical spanner is found, we may not be in the first + * row. + */ + + if (TBL_CELL_DOWN == c && rp == tbl->first_row) { + mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, ln, *pos, NULL); + return(0); + } + + (*pos)++; + + /* Extra check for the double-vertical. */ + + if (TBL_CELL_VERT == c && '|' == p[*pos]) { + (*pos)++; + c = TBL_CELL_DVERT; + } + + /* Disallow adjacent spacers. */ + + if (rp->last && (TBL_CELL_VERT == c || TBL_CELL_DVERT == c) && + (TBL_CELL_VERT == rp->last->pos || + TBL_CELL_DVERT == rp->last->pos)) { + mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, ln, *pos - 1, NULL); + return(0); + } + + /* Allocate cell then parse its modifiers. */ + + return(mods(tbl, cell_alloc(tbl, rp, c), ln, p, pos)); +} + + +static void +row(struct tbl_node *tbl, int ln, const char *p, int *pos) +{ + struct tbl_row *rp; + +row: /* + * EBNF describing this section: + * + * row ::= row_list [:space:]* [.]?[\n] + * row_list ::= [:space:]* row_elem row_tail + * row_tail ::= [:space:]*[,] row_list | + * epsilon + * row_elem ::= [\t\ ]*[:alpha:]+ + */ + + rp = mandoc_calloc(1, sizeof(struct tbl_row)); + if (tbl->last_row) { + tbl->last_row->next = rp; + tbl->last_row = rp; + } else + tbl->last_row = tbl->first_row = rp; + +cell: + while (isspace((unsigned char)p[*pos])) + (*pos)++; + + /* Safely exit layout context. */ + + if ('.' == p[*pos]) { + tbl->part = TBL_PART_DATA; + if (NULL == tbl->first_row) + mandoc_msg(MANDOCERR_TBLNOLAYOUT, tbl->parse, + ln, *pos, NULL); + (*pos)++; + return; + } + + /* End (and possibly restart) a row. */ + + if (',' == p[*pos]) { + (*pos)++; + goto row; + } else if ('\0' == p[*pos]) + return; + + if ( ! cell(tbl, rp, ln, p, pos)) + return; + + goto cell; + /* NOTREACHED */ +} + +int +tbl_layout(struct tbl_node *tbl, int ln, const char *p) +{ + int pos; + + pos = 0; + row(tbl, ln, p, &pos); + + /* Always succeed. */ + return(1); +} + +static struct tbl_cell * +cell_alloc(struct tbl_node *tbl, struct tbl_row *rp, enum tbl_cellt pos) +{ + struct tbl_cell *p, *pp; + struct tbl_head *h, *hp; + + p = mandoc_calloc(1, sizeof(struct tbl_cell)); + + if (NULL != (pp = rp->last)) { + rp->last->next = p; + rp->last = p; + } else + rp->last = rp->first = p; + + p->pos = pos; + + /* + * This is a little bit complicated. Here we determine the + * header the corresponds to a cell. We add headers dynamically + * when need be or re-use them, otherwise. As an example, given + * the following: + * + * 1 c || l + * 2 | c | l + * 3 l l + * 3 || c | l |. + * + * We first add the new headers (as there are none) in (1); then + * in (2) we insert the first spanner (as it doesn't match up + * with the header); then we re-use the prior data headers, + * skipping over the spanners; then we re-use everything and add + * a last spanner. Note that VERT headers are made into DVERT + * ones. + */ + + h = pp ? pp->head->next : tbl->first_head; + + if (h) { + /* Re-use data header. */ + if (TBL_HEAD_DATA == h->pos && + (TBL_CELL_VERT != p->pos && + TBL_CELL_DVERT != p->pos)) { + p->head = h; + return(p); + } + + /* Re-use spanner header. */ + if (TBL_HEAD_DATA != h->pos && + (TBL_CELL_VERT == p->pos || + TBL_CELL_DVERT == p->pos)) { + head_adjust(p, h); + p->head = h; + return(p); + } + + /* Right-shift headers with a new spanner. */ + if (TBL_HEAD_DATA == h->pos && + (TBL_CELL_VERT == p->pos || + TBL_CELL_DVERT == p->pos)) { + hp = mandoc_calloc(1, sizeof(struct tbl_head)); + hp->ident = tbl->opts.cols++; + hp->prev = h->prev; + if (h->prev) + h->prev->next = hp; + if (h == tbl->first_head) + tbl->first_head = hp; + h->prev = hp; + hp->next = h; + head_adjust(p, hp); + p->head = hp; + return(p); + } + + if (NULL != (h = h->next)) { + head_adjust(p, h); + p->head = h; + return(p); + } + + /* Fall through to default case... */ + } + + hp = mandoc_calloc(1, sizeof(struct tbl_head)); + hp->ident = tbl->opts.cols++; + + if (tbl->last_head) { + hp->prev = tbl->last_head; + tbl->last_head->next = hp; + tbl->last_head = hp; + } else + tbl->last_head = tbl->first_head = hp; + + head_adjust(p, hp); + p->head = hp; + return(p); +} + +static void +head_adjust(const struct tbl_cell *cellp, struct tbl_head *head) +{ + if (TBL_CELL_VERT != cellp->pos && + TBL_CELL_DVERT != cellp->pos) { + head->pos = TBL_HEAD_DATA; + return; + } + + if (TBL_CELL_VERT == cellp->pos) + if (TBL_HEAD_DVERT != head->pos) + head->pos = TBL_HEAD_VERT; + + if (TBL_CELL_DVERT == cellp->pos) + head->pos = TBL_HEAD_DVERT; +} + diff --git a/tbl_opts.c b/tbl_opts.c new file mode 100644 index 000000000000..5bd67f80ee3a --- /dev/null +++ b/tbl_opts.c @@ -0,0 +1,270 @@ +/* $Id: tbl_opts.c,v 1.12 2011/09/18 14:14:15 schwarze Exp $ */ +/* + * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "mandoc.h" +#include "libmandoc.h" +#include "libroff.h" + +enum tbl_ident { + KEY_CENTRE = 0, + KEY_DELIM, + KEY_EXPAND, + KEY_BOX, + KEY_DBOX, + KEY_ALLBOX, + KEY_TAB, + KEY_LINESIZE, + KEY_NOKEEP, + KEY_DPOINT, + KEY_NOSPACE, + KEY_FRAME, + KEY_DFRAME, + KEY_MAX +}; + +struct tbl_phrase { + const char *name; + int key; + enum tbl_ident ident; +}; + +/* Handle Commonwealth/American spellings. */ +#define KEY_MAXKEYS 14 + +/* Maximum length of key name string. */ +#define KEY_MAXNAME 13 + +/* Maximum length of key number size. */ +#define KEY_MAXNUMSZ 10 + +static const struct tbl_phrase keys[KEY_MAXKEYS] = { + { "center", TBL_OPT_CENTRE, KEY_CENTRE}, + { "centre", TBL_OPT_CENTRE, KEY_CENTRE}, + { "delim", 0, KEY_DELIM}, + { "expand", TBL_OPT_EXPAND, KEY_EXPAND}, + { "box", TBL_OPT_BOX, KEY_BOX}, + { "doublebox", TBL_OPT_DBOX, KEY_DBOX}, + { "allbox", TBL_OPT_ALLBOX, KEY_ALLBOX}, + { "frame", TBL_OPT_BOX, KEY_FRAME}, + { "doubleframe", TBL_OPT_DBOX, KEY_DFRAME}, + { "tab", 0, KEY_TAB}, + { "linesize", 0, KEY_LINESIZE}, + { "nokeep", TBL_OPT_NOKEEP, KEY_NOKEEP}, + { "decimalpoint", 0, KEY_DPOINT}, + { "nospaces", TBL_OPT_NOSPACE, KEY_NOSPACE}, +}; + +static int arg(struct tbl_node *, int, + const char *, int *, enum tbl_ident); +static void opt(struct tbl_node *, int, + const char *, int *); + +static int +arg(struct tbl_node *tbl, int ln, const char *p, int *pos, enum tbl_ident key) +{ + int i; + char buf[KEY_MAXNUMSZ]; + + while (isspace((unsigned char)p[*pos])) + (*pos)++; + + /* Arguments always begin with a parenthesis. */ + + if ('(' != p[*pos]) { + mandoc_msg(MANDOCERR_TBL, tbl->parse, + ln, *pos, NULL); + return(0); + } + + (*pos)++; + + /* + * The arguments can be ANY value, so we can't just stop at the + * next close parenthesis (the argument can be a closed + * parenthesis itself). + */ + + switch (key) { + case (KEY_DELIM): + if ('\0' == p[(*pos)++]) { + mandoc_msg(MANDOCERR_TBL, tbl->parse, + ln, *pos - 1, NULL); + return(0); + } + + if ('\0' == p[(*pos)++]) { + mandoc_msg(MANDOCERR_TBL, tbl->parse, + ln, *pos - 1, NULL); + return(0); + } + break; + case (KEY_TAB): + if ('\0' != (tbl->opts.tab = p[(*pos)++])) + break; + + mandoc_msg(MANDOCERR_TBL, tbl->parse, + ln, *pos - 1, NULL); + return(0); + case (KEY_LINESIZE): + for (i = 0; i < KEY_MAXNUMSZ && p[*pos]; i++, (*pos)++) { + buf[i] = p[*pos]; + if ( ! isdigit((unsigned char)buf[i])) + break; + } + + if (i < KEY_MAXNUMSZ) { + buf[i] = '\0'; + tbl->opts.linesize = atoi(buf); + break; + } + + mandoc_msg(MANDOCERR_TBL, tbl->parse, ln, *pos, NULL); + return(0); + case (KEY_DPOINT): + if ('\0' != (tbl->opts.decimal = p[(*pos)++])) + break; + + mandoc_msg(MANDOCERR_TBL, tbl->parse, + ln, *pos - 1, NULL); + return(0); + default: + abort(); + /* NOTREACHED */ + } + + /* End with a close parenthesis. */ + + if (')' == p[(*pos)++]) + return(1); + + mandoc_msg(MANDOCERR_TBL, tbl->parse, ln, *pos - 1, NULL); + return(0); +} + +static void +opt(struct tbl_node *tbl, int ln, const char *p, int *pos) +{ + int i, sv; + char buf[KEY_MAXNAME]; + + /* + * Parse individual options from the stream as surrounded by + * this goto. Each pass through the routine parses out a single + * option and registers it. Option arguments are processed in + * the arg() function. + */ + +again: /* + * EBNF describing this section: + * + * options ::= option_list [:space:]* [;][\n] + * option_list ::= option option_tail + * option_tail ::= [:space:]+ option_list | + * ::= epsilon + * option ::= [:alpha:]+ args + * args ::= [:space:]* [(] [:alpha:]+ [)] + */ + + while (isspace((unsigned char)p[*pos])) + (*pos)++; + + /* Safe exit point. */ + + if (';' == p[*pos]) + return; + + /* Copy up to first non-alpha character. */ + + for (sv = *pos, i = 0; i < KEY_MAXNAME; i++, (*pos)++) { + buf[i] = (char)tolower((unsigned char)p[*pos]); + if ( ! isalpha((unsigned char)buf[i])) + break; + } + + /* Exit if buffer is empty (or overrun). */ + + if (KEY_MAXNAME == i || 0 == i) { + mandoc_msg(MANDOCERR_TBL, tbl->parse, ln, *pos, NULL); + return; + } + + buf[i] = '\0'; + + while (isspace((unsigned char)p[*pos])) + (*pos)++; + + /* + * Look through all of the available keys to find one that + * matches the input. FIXME: hashtable this. + */ + + for (i = 0; i < KEY_MAXKEYS; i++) { + if (strcmp(buf, keys[i].name)) + continue; + + /* + * Note: this is more difficult to recover from, as we + * can be anywhere in the option sequence and it's + * harder to jump to the next. Meanwhile, just bail out + * of the sequence altogether. + */ + + if (keys[i].key) + tbl->opts.opts |= keys[i].key; + else if ( ! arg(tbl, ln, p, pos, keys[i].ident)) + return; + + break; + } + + /* + * Allow us to recover from bad options by continuing to another + * parse sequence. + */ + + if (KEY_MAXKEYS == i) + mandoc_msg(MANDOCERR_TBLOPT, tbl->parse, ln, sv, NULL); + + goto again; + /* NOTREACHED */ +} + +int +tbl_option(struct tbl_node *tbl, int ln, const char *p) +{ + int pos; + + /* + * Table options are always on just one line, so automatically + * switch into the next input mode here. + */ + tbl->part = TBL_PART_LAYOUT; + + pos = 0; + opt(tbl, ln, p, &pos); + + /* Always succeed. */ + return(1); +} diff --git a/tbl_term.c b/tbl_term.c new file mode 100644 index 000000000000..f1928f02cb20 --- /dev/null +++ b/tbl_term.c @@ -0,0 +1,444 @@ +/* $Id: tbl_term.c,v 1.21 2011/09/20 23:05:49 schwarze Exp $ */ +/* + * Copyright (c) 2009, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "mandoc.h" +#include "out.h" +#include "term.h" + +static size_t term_tbl_len(size_t, void *); +static size_t term_tbl_strlen(const char *, void *); +static void tbl_char(struct termp *, char, size_t); +static void tbl_data(struct termp *, const struct tbl *, + const struct tbl_dat *, + const struct roffcol *); +static size_t tbl_rulewidth(struct termp *, const struct tbl_head *); +static void tbl_hframe(struct termp *, const struct tbl_span *, int); +static void tbl_literal(struct termp *, const struct tbl_dat *, + const struct roffcol *); +static void tbl_number(struct termp *, const struct tbl *, + const struct tbl_dat *, + const struct roffcol *); +static void tbl_hrule(struct termp *, const struct tbl_span *); +static void tbl_vrule(struct termp *, const struct tbl_head *); + + +static size_t +term_tbl_strlen(const char *p, void *arg) +{ + + return(term_strlen((const struct termp *)arg, p)); +} + +static size_t +term_tbl_len(size_t sz, void *arg) +{ + + return(term_len((const struct termp *)arg, sz)); +} + +void +term_tbl(struct termp *tp, const struct tbl_span *sp) +{ + const struct tbl_head *hp; + const struct tbl_dat *dp; + struct roffcol *col; + int spans; + size_t rmargin, maxrmargin; + + rmargin = tp->rmargin; + maxrmargin = tp->maxrmargin; + + tp->rmargin = tp->maxrmargin = TERM_MAXMARGIN; + + /* Inhibit printing of spaces: we do padding ourselves. */ + + tp->flags |= TERMP_NONOSPACE; + tp->flags |= TERMP_NOSPACE; + + /* + * The first time we're invoked for a given table block, + * calculate the table widths and decimal positions. + */ + + if (TBL_SPAN_FIRST & sp->flags) { + term_flushln(tp); + + tp->tbl.len = term_tbl_len; + tp->tbl.slen = term_tbl_strlen; + tp->tbl.arg = tp; + + tblcalc(&tp->tbl, sp); + } + + /* Horizontal frame at the start of boxed tables. */ + + if (TBL_SPAN_FIRST & sp->flags) { + if (TBL_OPT_DBOX & sp->tbl->opts) + tbl_hframe(tp, sp, 1); + if (TBL_OPT_DBOX & sp->tbl->opts || + TBL_OPT_BOX & sp->tbl->opts) + tbl_hframe(tp, sp, 0); + } + + /* Vertical frame at the start of each row. */ + + if (TBL_OPT_BOX & sp->tbl->opts || TBL_OPT_DBOX & sp->tbl->opts) + term_word(tp, TBL_SPAN_HORIZ == sp->pos || + TBL_SPAN_DHORIZ == sp->pos ? "+" : "|"); + + /* + * Now print the actual data itself depending on the span type. + * Spanner spans get a horizontal rule; data spanners have their + * data printed by matching data to header. + */ + + switch (sp->pos) { + case (TBL_SPAN_HORIZ): + /* FALLTHROUGH */ + case (TBL_SPAN_DHORIZ): + tbl_hrule(tp, sp); + break; + case (TBL_SPAN_DATA): + /* Iterate over template headers. */ + dp = sp->first; + spans = 0; + for (hp = sp->head; hp; hp = hp->next) { + /* + * If the current data header is invoked during + * a spanner ("spans" > 0), don't emit anything + * at all. + */ + switch (hp->pos) { + case (TBL_HEAD_VERT): + /* FALLTHROUGH */ + case (TBL_HEAD_DVERT): + if (spans <= 0) + tbl_vrule(tp, hp); + continue; + case (TBL_HEAD_DATA): + break; + } + + if (--spans >= 0) + continue; + + /* + * All cells get a leading blank, except the + * first one and those after double rulers. + */ + + if (hp->prev && TBL_HEAD_DVERT != hp->prev->pos) + tbl_char(tp, ASCII_NBRSP, 1); + + col = &tp->tbl.cols[hp->ident]; + tbl_data(tp, sp->tbl, dp, col); + + /* No trailing blanks. */ + + if (NULL == hp->next) + break; + + /* + * Add another blank between cells, + * or two when there is no vertical ruler. + */ + + tbl_char(tp, ASCII_NBRSP, + TBL_HEAD_VERT == hp->next->pos || + TBL_HEAD_DVERT == hp->next->pos ? 1 : 2); + + /* + * Go to the next data cell and assign the + * number of subsequent spans, if applicable. + */ + + if (dp) { + spans = dp->spans; + dp = dp->next; + } + } + break; + } + + /* Vertical frame at the end of each row. */ + + if (TBL_OPT_BOX & sp->tbl->opts || TBL_OPT_DBOX & sp->tbl->opts) + term_word(tp, TBL_SPAN_HORIZ == sp->pos || + TBL_SPAN_DHORIZ == sp->pos ? "+" : " |"); + term_flushln(tp); + + /* + * If we're the last row, clean up after ourselves: clear the + * existing table configuration and set it to NULL. + */ + + if (TBL_SPAN_LAST & sp->flags) { + if (TBL_OPT_DBOX & sp->tbl->opts || + TBL_OPT_BOX & sp->tbl->opts) + tbl_hframe(tp, sp, 0); + if (TBL_OPT_DBOX & sp->tbl->opts) + tbl_hframe(tp, sp, 1); + assert(tp->tbl.cols); + free(tp->tbl.cols); + tp->tbl.cols = NULL; + } + + tp->flags &= ~TERMP_NONOSPACE; + tp->rmargin = rmargin; + tp->maxrmargin = maxrmargin; + +} + +/* + * Horizontal rules extend across the entire table. + * Calculate the width by iterating over columns. + */ +static size_t +tbl_rulewidth(struct termp *tp, const struct tbl_head *hp) +{ + size_t width; + + width = tp->tbl.cols[hp->ident].width; + if (TBL_HEAD_DATA == hp->pos) { + /* Account for leading blanks. */ + if (hp->prev && TBL_HEAD_DVERT != hp->prev->pos) + width++; + /* Account for trailing blanks. */ + width++; + if (hp->next && + TBL_HEAD_VERT != hp->next->pos && + TBL_HEAD_DVERT != hp->next->pos) + width++; + } + return(width); +} + +/* + * Rules inside the table can be single or double + * and have crossings with vertical rules marked with pluses. + */ +static void +tbl_hrule(struct termp *tp, const struct tbl_span *sp) +{ + const struct tbl_head *hp; + char c; + + c = '-'; + if (TBL_SPAN_DHORIZ == sp->pos) + c = '='; + + for (hp = sp->head; hp; hp = hp->next) + tbl_char(tp, + TBL_HEAD_DATA == hp->pos ? c : '+', + tbl_rulewidth(tp, hp)); +} + +/* + * Rules above and below the table are always single + * and have an additional plus at the beginning and end. + * For double frames, this function is called twice, + * and the outer one does not have crossings. + */ +static void +tbl_hframe(struct termp *tp, const struct tbl_span *sp, int outer) +{ + const struct tbl_head *hp; + + term_word(tp, "+"); + for (hp = sp->head; hp; hp = hp->next) + tbl_char(tp, + outer || TBL_HEAD_DATA == hp->pos ? '-' : '+', + tbl_rulewidth(tp, hp)); + term_word(tp, "+"); + term_flushln(tp); +} + +static void +tbl_data(struct termp *tp, const struct tbl *tbl, + const struct tbl_dat *dp, + const struct roffcol *col) +{ + + if (NULL == dp) { + tbl_char(tp, ASCII_NBRSP, col->width); + return; + } + assert(dp->layout); + + switch (dp->pos) { + case (TBL_DATA_NONE): + tbl_char(tp, ASCII_NBRSP, col->width); + return; + case (TBL_DATA_HORIZ): + /* FALLTHROUGH */ + case (TBL_DATA_NHORIZ): + tbl_char(tp, '-', col->width); + return; + case (TBL_DATA_NDHORIZ): + /* FALLTHROUGH */ + case (TBL_DATA_DHORIZ): + tbl_char(tp, '=', col->width); + return; + default: + break; + } + + switch (dp->layout->pos) { + case (TBL_CELL_HORIZ): + tbl_char(tp, '-', col->width); + break; + case (TBL_CELL_DHORIZ): + tbl_char(tp, '=', col->width); + break; + case (TBL_CELL_LONG): + /* FALLTHROUGH */ + case (TBL_CELL_CENTRE): + /* FALLTHROUGH */ + case (TBL_CELL_LEFT): + /* FALLTHROUGH */ + case (TBL_CELL_RIGHT): + tbl_literal(tp, dp, col); + break; + case (TBL_CELL_NUMBER): + tbl_number(tp, tbl, dp, col); + break; + case (TBL_CELL_DOWN): + tbl_char(tp, ASCII_NBRSP, col->width); + break; + default: + abort(); + /* NOTREACHED */ + } +} + +static void +tbl_vrule(struct termp *tp, const struct tbl_head *hp) +{ + + switch (hp->pos) { + case (TBL_HEAD_VERT): + term_word(tp, "|"); + break; + case (TBL_HEAD_DVERT): + term_word(tp, "||"); + break; + default: + break; + } +} + +static void +tbl_char(struct termp *tp, char c, size_t len) +{ + size_t i, sz; + char cp[2]; + + cp[0] = c; + cp[1] = '\0'; + + sz = term_strlen(tp, cp); + + for (i = 0; i < len; i += sz) + term_word(tp, cp); +} + +static void +tbl_literal(struct termp *tp, const struct tbl_dat *dp, + const struct roffcol *col) +{ + size_t len, padl, padr; + + assert(dp->string); + len = term_strlen(tp, dp->string); + padr = col->width > len ? col->width - len : 0; + padl = 0; + + switch (dp->layout->pos) { + case (TBL_CELL_LONG): + padl = term_len(tp, 1); + padr = padr > padl ? padr - padl : 0; + break; + case (TBL_CELL_CENTRE): + if (2 > padr) + break; + padl = padr / 2; + padr -= padl; + break; + case (TBL_CELL_RIGHT): + padl = padr; + padr = 0; + break; + default: + break; + } + + tbl_char(tp, ASCII_NBRSP, padl); + term_word(tp, dp->string); + tbl_char(tp, ASCII_NBRSP, padr); +} + +static void +tbl_number(struct termp *tp, const struct tbl *tbl, + const struct tbl_dat *dp, + const struct roffcol *col) +{ + char *cp; + char buf[2]; + size_t sz, psz, ssz, d, padl; + int i; + + /* + * See calc_data_number(). Left-pad by taking the offset of our + * and the maximum decimal; right-pad by the remaining amount. + */ + + assert(dp->string); + + sz = term_strlen(tp, dp->string); + + buf[0] = tbl->decimal; + buf[1] = '\0'; + + psz = term_strlen(tp, buf); + + if (NULL != (cp = strrchr(dp->string, tbl->decimal))) { + buf[1] = '\0'; + for (ssz = 0, i = 0; cp != &dp->string[i]; i++) { + buf[0] = dp->string[i]; + ssz += term_strlen(tp, buf); + } + d = ssz + psz; + } else + d = sz + psz; + + padl = col->decimal - d; + + tbl_char(tp, ASCII_NBRSP, padl); + term_word(tp, dp->string); + if (col->width > sz + padl) + tbl_char(tp, ASCII_NBRSP, col->width - sz - padl); +} + diff --git a/term.c b/term.c new file mode 100644 index 000000000000..4ca15ed6fa58 --- /dev/null +++ b/term.c @@ -0,0 +1,736 @@ +/* $Id: term.c,v 1.201 2011/09/21 09:57:13 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * Copyright (c) 2010, 2011 Ingo Schwarze <schwarze@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/types.h> + +#include <assert.h> +#include <ctype.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "mandoc.h" +#include "out.h" +#include "term.h" +#include "main.h" + +static void adjbuf(struct termp *p, int); +static void bufferc(struct termp *, char); +static void encode(struct termp *, const char *, size_t); +static void encode1(struct termp *, int); + +void +term_free(struct termp *p) +{ + + if (p->buf) + free(p->buf); + if (p->symtab) + mchars_free(p->symtab); + + free(p); +} + + +void +term_begin(struct termp *p, term_margin head, + term_margin foot, const void *arg) +{ + + p->headf = head; + p->footf = foot; + p->argf = arg; + (*p->begin)(p); +} + + +void +term_end(struct termp *p) +{ + + (*p->end)(p); +} + +/* + * Flush a line of text. A "line" is loosely defined as being something + * that should be followed by a newline, regardless of whether it's + * broken apart by newlines getting there. A line can also be a + * fragment of a columnar list (`Bl -tag' or `Bl -column'), which does + * not have a trailing newline. + * + * The following flags may be specified: + * + * - TERMP_NOBREAK: this is the most important and is used when making + * columns. In short: don't print a newline and instead expect the + * next call to do the padding up to the start of the next column. + * + * - TERMP_TWOSPACE: make sure there is room for at least two space + * characters of padding. Otherwise, rather break the line. + * + * - TERMP_DANGLE: don't newline when TERMP_NOBREAK is specified and + * the line is overrun, and don't pad-right if it's underrun. + * + * - TERMP_HANG: like TERMP_DANGLE, but doesn't newline when + * overrunning, instead save the position and continue at that point + * when the next invocation. + * + * In-line line breaking: + * + * If TERMP_NOBREAK is specified and the line overruns the right + * margin, it will break and pad-right to the right margin after + * writing. If maxrmargin is violated, it will break and continue + * writing from the right-margin, which will lead to the above scenario + * upon exit. Otherwise, the line will break at the right margin. + */ +void +term_flushln(struct termp *p) +{ + int i; /* current input position in p->buf */ + size_t vis; /* current visual position on output */ + size_t vbl; /* number of blanks to prepend to output */ + size_t vend; /* end of word visual position on output */ + size_t bp; /* visual right border position */ + size_t dv; /* temporary for visual pos calculations */ + int j; /* temporary loop index for p->buf */ + int jhy; /* last hyph before overflow w/r/t j */ + size_t maxvis; /* output position of visible boundary */ + size_t mmax; /* used in calculating bp */ + + /* + * First, establish the maximum columns of "visible" content. + * This is usually the difference between the right-margin and + * an indentation, but can be, for tagged lists or columns, a + * small set of values. + */ + assert (p->rmargin >= p->offset); + dv = p->rmargin - p->offset; + maxvis = (int)dv > p->overstep ? dv - (size_t)p->overstep : 0; + dv = p->maxrmargin - p->offset; + mmax = (int)dv > p->overstep ? dv - (size_t)p->overstep : 0; + + bp = TERMP_NOBREAK & p->flags ? mmax : maxvis; + + /* + * Calculate the required amount of padding. + */ + vbl = p->offset + p->overstep > p->viscol ? + p->offset + p->overstep - p->viscol : 0; + + vis = vend = 0; + i = 0; + + while (i < p->col) { + /* + * Handle literal tab characters: collapse all + * subsequent tabs into a single huge set of spaces. + */ + while (i < p->col && '\t' == p->buf[i]) { + vend = (vis / p->tabwidth + 1) * p->tabwidth; + vbl += vend - vis; + vis = vend; + i++; + } + + /* + * Count up visible word characters. Control sequences + * (starting with the CSI) aren't counted. A space + * generates a non-printing word, which is valid (the + * space is printed according to regular spacing rules). + */ + + for (j = i, jhy = 0; j < p->col; j++) { + if ((j && ' ' == p->buf[j]) || '\t' == p->buf[j]) + break; + + /* Back over the the last printed character. */ + if (8 == p->buf[j]) { + assert(j); + vend -= (*p->width)(p, p->buf[j - 1]); + continue; + } + + /* Regular word. */ + /* Break at the hyphen point if we overrun. */ + if (vend > vis && vend < bp && + ASCII_HYPH == p->buf[j]) + jhy = j; + + vend += (*p->width)(p, p->buf[j]); + } + + /* + * Find out whether we would exceed the right margin. + * If so, break to the next line. + */ + if (vend > bp && 0 == jhy && vis > 0) { + vend -= vis; + (*p->endline)(p); + p->viscol = 0; + if (TERMP_NOBREAK & p->flags) { + vbl = p->rmargin; + vend += p->rmargin - p->offset; + } else + vbl = p->offset; + + /* Remove the p->overstep width. */ + + bp += (size_t)p->overstep; + p->overstep = 0; + } + + /* Write out the [remaining] word. */ + for ( ; i < p->col; i++) { + if (vend > bp && jhy > 0 && i > jhy) + break; + if ('\t' == p->buf[i]) + break; + if (' ' == p->buf[i]) { + j = i; + while (' ' == p->buf[i]) + i++; + dv = (size_t)(i - j) * (*p->width)(p, ' '); + vbl += dv; + vend += dv; + break; + } + if (ASCII_NBRSP == p->buf[i]) { + vbl += (*p->width)(p, ' '); + continue; + } + + /* + * Now we definitely know there will be + * printable characters to output, + * so write preceding white space now. + */ + if (vbl) { + (*p->advance)(p, vbl); + p->viscol += vbl; + vbl = 0; + } + + if (ASCII_HYPH == p->buf[i]) { + (*p->letter)(p, '-'); + p->viscol += (*p->width)(p, '-'); + continue; + } + + (*p->letter)(p, p->buf[i]); + if (8 == p->buf[i]) + p->viscol -= (*p->width)(p, p->buf[i-1]); + else + p->viscol += (*p->width)(p, p->buf[i]); + } + vis = vend; + } + + /* + * If there was trailing white space, it was not printed; + * so reset the cursor position accordingly. + */ + if (vis) + vis -= vbl; + + p->col = 0; + p->overstep = 0; + + if ( ! (TERMP_NOBREAK & p->flags)) { + p->viscol = 0; + (*p->endline)(p); + return; + } + + if (TERMP_HANG & p->flags) { + /* We need one blank after the tag. */ + p->overstep = (int)(vis - maxvis + (*p->width)(p, ' ')); + + /* + * Behave exactly the same way as groff: + * If we have overstepped the margin, temporarily move + * it to the right and flag the rest of the line to be + * shorter. + * If we landed right at the margin, be happy. + * If we are one step before the margin, temporarily + * move it one step LEFT and flag the rest of the line + * to be longer. + */ + if (p->overstep < -1) + p->overstep = 0; + return; + + } else if (TERMP_DANGLE & p->flags) + return; + + /* If the column was overrun, break the line. */ + if (maxvis <= vis + + ((TERMP_TWOSPACE & p->flags) ? (*p->width)(p, ' ') : 0)) { + (*p->endline)(p); + p->viscol = 0; + } +} + + +/* + * A newline only breaks an existing line; it won't assert vertical + * space. All data in the output buffer is flushed prior to the newline + * assertion. + */ +void +term_newln(struct termp *p) +{ + + p->flags |= TERMP_NOSPACE; + if (p->col || p->viscol) + term_flushln(p); +} + + +/* + * Asserts a vertical space (a full, empty line-break between lines). + * Note that if used twice, this will cause two blank spaces and so on. + * All data in the output buffer is flushed prior to the newline + * assertion. + */ +void +term_vspace(struct termp *p) +{ + + term_newln(p); + p->viscol = 0; + (*p->endline)(p); +} + +void +term_fontlast(struct termp *p) +{ + enum termfont f; + + f = p->fontl; + p->fontl = p->fontq[p->fonti]; + p->fontq[p->fonti] = f; +} + + +void +term_fontrepl(struct termp *p, enum termfont f) +{ + + p->fontl = p->fontq[p->fonti]; + p->fontq[p->fonti] = f; +} + + +void +term_fontpush(struct termp *p, enum termfont f) +{ + + assert(p->fonti + 1 < 10); + p->fontl = p->fontq[p->fonti]; + p->fontq[++p->fonti] = f; +} + + +const void * +term_fontq(struct termp *p) +{ + + return(&p->fontq[p->fonti]); +} + + +enum termfont +term_fonttop(struct termp *p) +{ + + return(p->fontq[p->fonti]); +} + + +void +term_fontpopq(struct termp *p, const void *key) +{ + + while (p->fonti >= 0 && key != &p->fontq[p->fonti]) + p->fonti--; + assert(p->fonti >= 0); +} + + +void +term_fontpop(struct termp *p) +{ + + assert(p->fonti); + p->fonti--; +} + +/* + * Handle pwords, partial words, which may be either a single word or a + * phrase that cannot be broken down (such as a literal string). This + * handles word styling. + */ +void +term_word(struct termp *p, const char *word) +{ + const char *seq, *cp; + char c; + int sz, uc; + size_t ssz; + enum mandoc_esc esc; + + if ( ! (TERMP_NOSPACE & p->flags)) { + if ( ! (TERMP_KEEP & p->flags)) { + if (TERMP_PREKEEP & p->flags) + p->flags |= TERMP_KEEP; + bufferc(p, ' '); + if (TERMP_SENTENCE & p->flags) + bufferc(p, ' '); + } else + bufferc(p, ASCII_NBRSP); + } + + if ( ! (p->flags & TERMP_NONOSPACE)) + p->flags &= ~TERMP_NOSPACE; + else + p->flags |= TERMP_NOSPACE; + + p->flags &= ~(TERMP_SENTENCE | TERMP_IGNDELIM); + + while ('\0' != *word) { + if ((ssz = strcspn(word, "\\")) > 0) + encode(p, word, ssz); + + word += (int)ssz; + if ('\\' != *word) + continue; + + word++; + esc = mandoc_escape(&word, &seq, &sz); + if (ESCAPE_ERROR == esc) + break; + + if (TERMENC_ASCII != p->enc) + switch (esc) { + case (ESCAPE_UNICODE): + uc = mchars_num2uc(seq + 1, sz - 1); + if ('\0' == uc) + break; + encode1(p, uc); + continue; + case (ESCAPE_SPECIAL): + uc = mchars_spec2cp(p->symtab, seq, sz); + if (uc <= 0) + break; + encode1(p, uc); + continue; + default: + break; + } + + switch (esc) { + case (ESCAPE_UNICODE): + encode1(p, '?'); + break; + case (ESCAPE_NUMBERED): + c = mchars_num2char(seq, sz); + if ('\0' != c) + encode(p, &c, 1); + break; + case (ESCAPE_SPECIAL): + cp = mchars_spec2str(p->symtab, seq, sz, &ssz); + if (NULL != cp) + encode(p, cp, ssz); + else if (1 == ssz) + encode(p, seq, sz); + break; + case (ESCAPE_FONTBOLD): + term_fontrepl(p, TERMFONT_BOLD); + break; + case (ESCAPE_FONTITALIC): + term_fontrepl(p, TERMFONT_UNDER); + break; + case (ESCAPE_FONT): + /* FALLTHROUGH */ + case (ESCAPE_FONTROMAN): + term_fontrepl(p, TERMFONT_NONE); + break; + case (ESCAPE_FONTPREV): + term_fontlast(p); + break; + case (ESCAPE_NOSPACE): + if ('\0' == *word) + p->flags |= TERMP_NOSPACE; + break; + default: + break; + } + } +} + +static void +adjbuf(struct termp *p, int sz) +{ + + if (0 == p->maxcols) + p->maxcols = 1024; + while (sz >= p->maxcols) + p->maxcols <<= 2; + + p->buf = mandoc_realloc + (p->buf, sizeof(int) * (size_t)p->maxcols); +} + +static void +bufferc(struct termp *p, char c) +{ + + if (p->col + 1 >= p->maxcols) + adjbuf(p, p->col + 1); + + p->buf[p->col++] = c; +} + +/* + * See encode(). + * Do this for a single (probably unicode) value. + * Does not check for non-decorated glyphs. + */ +static void +encode1(struct termp *p, int c) +{ + enum termfont f; + + if (p->col + 4 >= p->maxcols) + adjbuf(p, p->col + 4); + + f = term_fonttop(p); + + if (TERMFONT_NONE == f) { + p->buf[p->col++] = c; + return; + } else if (TERMFONT_UNDER == f) { + p->buf[p->col++] = '_'; + } else + p->buf[p->col++] = c; + + p->buf[p->col++] = 8; + p->buf[p->col++] = c; +} + +static void +encode(struct termp *p, const char *word, size_t sz) +{ + enum termfont f; + int i, len; + + /* LINTED */ + len = sz; + + /* + * Encode and buffer a string of characters. If the current + * font mode is unset, buffer directly, else encode then buffer + * character by character. + */ + + if (TERMFONT_NONE == (f = term_fonttop(p))) { + if (p->col + len >= p->maxcols) + adjbuf(p, p->col + len); + for (i = 0; i < len; i++) + p->buf[p->col++] = word[i]; + return; + } + + /* Pre-buffer, assuming worst-case. */ + + if (p->col + 1 + (len * 3) >= p->maxcols) + adjbuf(p, p->col + 1 + (len * 3)); + + for (i = 0; i < len; i++) { + if (ASCII_HYPH != word[i] && + ! isgraph((unsigned char)word[i])) { + p->buf[p->col++] = word[i]; + continue; + } + + if (TERMFONT_UNDER == f) + p->buf[p->col++] = '_'; + else if (ASCII_HYPH == word[i]) + p->buf[p->col++] = '-'; + else + p->buf[p->col++] = word[i]; + + p->buf[p->col++] = 8; + p->buf[p->col++] = word[i]; + } +} + +size_t +term_len(const struct termp *p, size_t sz) +{ + + return((*p->width)(p, ' ') * sz); +} + + +size_t +term_strlen(const struct termp *p, const char *cp) +{ + size_t sz, rsz, i; + int ssz, c; + const char *seq, *rhs; + enum mandoc_esc esc; + static const char rej[] = { '\\', ASCII_HYPH, ASCII_NBRSP, '\0' }; + + /* + * Account for escaped sequences within string length + * calculations. This follows the logic in term_word() as we + * must calculate the width of produced strings. + */ + + sz = 0; + while ('\0' != *cp) { + rsz = strcspn(cp, rej); + for (i = 0; i < rsz; i++) + sz += (*p->width)(p, *cp++); + + c = 0; + switch (*cp) { + case ('\\'): + cp++; + esc = mandoc_escape(&cp, &seq, &ssz); + if (ESCAPE_ERROR == esc) + return(sz); + + if (TERMENC_ASCII != p->enc) + switch (esc) { + case (ESCAPE_UNICODE): + c = mchars_num2uc + (seq + 1, ssz - 1); + if ('\0' == c) + break; + sz += (*p->width)(p, c); + continue; + case (ESCAPE_SPECIAL): + c = mchars_spec2cp + (p->symtab, seq, ssz); + if (c <= 0) + break; + sz += (*p->width)(p, c); + continue; + default: + break; + } + + rhs = NULL; + + switch (esc) { + case (ESCAPE_UNICODE): + sz += (*p->width)(p, '?'); + break; + case (ESCAPE_NUMBERED): + c = mchars_num2char(seq, ssz); + if ('\0' != c) + sz += (*p->width)(p, c); + break; + case (ESCAPE_SPECIAL): + rhs = mchars_spec2str + (p->symtab, seq, ssz, &rsz); + + if (ssz != 1 || rhs) + break; + + rhs = seq; + rsz = ssz; + break; + default: + break; + } + + if (NULL == rhs) + break; + + for (i = 0; i < rsz; i++) + sz += (*p->width)(p, *rhs++); + break; + case (ASCII_NBRSP): + sz += (*p->width)(p, ' '); + cp++; + break; + case (ASCII_HYPH): + sz += (*p->width)(p, '-'); + cp++; + break; + default: + break; + } + } + + return(sz); +} + +/* ARGSUSED */ +size_t +term_vspan(const struct termp *p, const struct roffsu *su) +{ + double r; + + switch (su->unit) { + case (SCALE_CM): + r = su->scale * 2; + break; + case (SCALE_IN): + r = su->scale * 6; + break; + case (SCALE_PC): + r = su->scale; + break; + case (SCALE_PT): + r = su->scale / 8; + break; + case (SCALE_MM): + r = su->scale / 1000; + break; + case (SCALE_VS): + r = su->scale; + break; + default: + r = su->scale - 1; + break; + } + + if (r < 0.0) + r = 0.0; + return(/* LINTED */(size_t) + r); +} + +size_t +term_hspan(const struct termp *p, const struct roffsu *su) +{ + double v; + + v = ((*p->hspan)(p, su)); + if (v < 0.0) + v = 0.0; + return((size_t) /* LINTED */ + v); +} diff --git a/term.h b/term.h new file mode 100644 index 000000000000..56d076e54a83 --- /dev/null +++ b/term.h @@ -0,0 +1,128 @@ +/* $Id: term.h,v 1.90 2011/12/04 23:10:52 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef TERM_H +#define TERM_H + +__BEGIN_DECLS + +struct termp; + +enum termenc { + TERMENC_ASCII, + TERMENC_LOCALE, + TERMENC_UTF8 +}; + +enum termtype { + TERMTYPE_CHAR, + TERMTYPE_PS, + TERMTYPE_PDF +}; + +enum termfont { + TERMFONT_NONE = 0, + TERMFONT_BOLD, + TERMFONT_UNDER, + TERMFONT__MAX +}; + +#define TERM_MAXMARGIN 100000 /* FIXME */ + +typedef void (*term_margin)(struct termp *, const void *); + +struct termp_tbl { + int width; /* width in fixed chars */ + int decimal; /* decimal point position */ +}; + +struct termp { + enum termtype type; + struct rofftbl tbl; /* table configuration */ + int mdocstyle; /* imitate mdoc(7) output */ + size_t defindent; /* Default indent for text. */ + size_t defrmargin; /* Right margin of the device. */ + size_t rmargin; /* Current right margin. */ + size_t maxrmargin; /* Max right margin. */ + int maxcols; /* Max size of buf. */ + size_t offset; /* Margin offest. */ + size_t tabwidth; /* Distance of tab positions. */ + int col; /* Bytes in buf. */ + size_t viscol; /* Chars on current line. */ + int overstep; /* See termp_flushln(). */ + int flags; +#define TERMP_SENTENCE (1 << 1) /* Space before a sentence. */ +#define TERMP_NOSPACE (1 << 2) /* No space before words. */ +#define TERMP_NOBREAK (1 << 4) /* See term_flushln(). */ +#define TERMP_IGNDELIM (1 << 6) /* Delims like regulars. */ +#define TERMP_NONOSPACE (1 << 7) /* No space (no autounset). */ +#define TERMP_DANGLE (1 << 8) /* See term_flushln(). */ +#define TERMP_HANG (1 << 9) /* See term_flushln(). */ +#define TERMP_TWOSPACE (1 << 10) /* See term_flushln(). */ +#define TERMP_NOSPLIT (1 << 11) /* See termp_an_pre/post(). */ +#define TERMP_SPLIT (1 << 12) /* See termp_an_pre/post(). */ +#define TERMP_ANPREC (1 << 13) /* See termp_an_pre(). */ +#define TERMP_KEEP (1 << 14) /* Keep words together. */ +#define TERMP_PREKEEP (1 << 15) /* ...starting with the next one. */ + int *buf; /* Output buffer. */ + enum termenc enc; /* Type of encoding. */ + struct mchars *symtab; /* Encoded-symbol table. */ + enum termfont fontl; /* Last font set. */ + enum termfont fontq[10]; /* Symmetric fonts. */ + int fonti; /* Index of font stack. */ + term_margin headf; /* invoked to print head */ + term_margin footf; /* invoked to print foot */ + void (*letter)(struct termp *, int); + void (*begin)(struct termp *); + void (*end)(struct termp *); + void (*endline)(struct termp *); + void (*advance)(struct termp *, size_t); + size_t (*width)(const struct termp *, int); + double (*hspan)(const struct termp *, + const struct roffsu *); + const void *argf; /* arg for headf/footf */ + struct termp_ps *ps; +}; + +void term_eqn(struct termp *, const struct eqn *); +void term_tbl(struct termp *, const struct tbl_span *); +void term_free(struct termp *); +void term_newln(struct termp *); +void term_vspace(struct termp *); +void term_word(struct termp *, const char *); +void term_flushln(struct termp *); +void term_begin(struct termp *, term_margin, + term_margin, const void *); +void term_end(struct termp *); + +size_t term_hspan(const struct termp *, + const struct roffsu *); +size_t term_vspan(const struct termp *, + const struct roffsu *); +size_t term_strlen(const struct termp *, const char *); +size_t term_len(const struct termp *, size_t); + +enum termfont term_fonttop(struct termp *); +const void *term_fontq(struct termp *); +void term_fontpush(struct termp *, enum termfont); +void term_fontpop(struct termp *); +void term_fontpopq(struct termp *, const void *); +void term_fontrepl(struct termp *, enum termfont); +void term_fontlast(struct termp *); + +__END_DECLS + +#endif /*!TERM_H*/ diff --git a/term_ascii.c b/term_ascii.c new file mode 100644 index 000000000000..2f114786f6af --- /dev/null +++ b/term_ascii.c @@ -0,0 +1,289 @@ +/* $Id: term_ascii.c,v 1.20 2011/12/04 23:10:52 schwarze Exp $ */ +/* + * Copyright (c) 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/types.h> + +#include <assert.h> +#ifdef USE_WCHAR +# include <locale.h> +#endif +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#ifdef USE_WCHAR +# include <wchar.h> +#endif + +#include "mandoc.h" +#include "out.h" +#include "term.h" +#include "main.h" + +/* + * Sadly, this doesn't seem to be defined on systems even when they + * support it. For the time being, remove it and let those compiling + * the software decide for themselves what to use. + */ +#if 0 +#if ! defined(__STDC_ISO_10646__) +# undef USE_WCHAR +#endif +#endif + +static struct termp *ascii_init(enum termenc, char *); +static double ascii_hspan(const struct termp *, + const struct roffsu *); +static size_t ascii_width(const struct termp *, int); +static void ascii_advance(struct termp *, size_t); +static void ascii_begin(struct termp *); +static void ascii_end(struct termp *); +static void ascii_endline(struct termp *); +static void ascii_letter(struct termp *, int); + +#ifdef USE_WCHAR +static void locale_advance(struct termp *, size_t); +static void locale_endline(struct termp *); +static void locale_letter(struct termp *, int); +static size_t locale_width(const struct termp *, int); +#endif + +static struct termp * +ascii_init(enum termenc enc, char *outopts) +{ + const char *toks[4]; + char *v; + struct termp *p; + + p = mandoc_calloc(1, sizeof(struct termp)); + p->enc = enc; + + p->tabwidth = 5; + p->defrmargin = 78; + + p->begin = ascii_begin; + p->end = ascii_end; + p->hspan = ascii_hspan; + p->type = TERMTYPE_CHAR; + + p->enc = TERMENC_ASCII; + p->advance = ascii_advance; + p->endline = ascii_endline; + p->letter = ascii_letter; + p->width = ascii_width; + +#ifdef USE_WCHAR + if (TERMENC_ASCII != enc) { + v = TERMENC_LOCALE == enc ? + setlocale(LC_ALL, "") : + setlocale(LC_CTYPE, "UTF-8"); + if (NULL != v && MB_CUR_MAX > 1) { + p->enc = enc; + p->advance = locale_advance; + p->endline = locale_endline; + p->letter = locale_letter; + p->width = locale_width; + } + } +#endif + + toks[0] = "indent"; + toks[1] = "width"; + toks[2] = "mdoc"; + toks[3] = NULL; + + while (outopts && *outopts) + switch (getsubopt(&outopts, UNCONST(toks), &v)) { + case (0): + p->defindent = (size_t)atoi(v); + break; + case (1): + p->defrmargin = (size_t)atoi(v); + break; + case (2): + /* + * Temporary, undocumented mode + * to imitate mdoc(7) output style. + */ + p->mdocstyle = 1; + p->defindent = 5; + break; + default: + break; + } + + /* Enforce a lower boundary. */ + if (p->defrmargin < 58) + p->defrmargin = 58; + + return(p); +} + +void * +ascii_alloc(char *outopts) +{ + + return(ascii_init(TERMENC_ASCII, outopts)); +} + +void * +utf8_alloc(char *outopts) +{ + + return(ascii_init(TERMENC_UTF8, outopts)); +} + + +void * +locale_alloc(char *outopts) +{ + + return(ascii_init(TERMENC_LOCALE, outopts)); +} + +/* ARGSUSED */ +static size_t +ascii_width(const struct termp *p, int c) +{ + + return(1); +} + +void +ascii_free(void *arg) +{ + + term_free((struct termp *)arg); +} + +/* ARGSUSED */ +static void +ascii_letter(struct termp *p, int c) +{ + + putchar(c); +} + +static void +ascii_begin(struct termp *p) +{ + + (*p->headf)(p, p->argf); +} + +static void +ascii_end(struct termp *p) +{ + + (*p->footf)(p, p->argf); +} + +/* ARGSUSED */ +static void +ascii_endline(struct termp *p) +{ + + putchar('\n'); +} + +/* ARGSUSED */ +static void +ascii_advance(struct termp *p, size_t len) +{ + size_t i; + + for (i = 0; i < len; i++) + putchar(' '); +} + +/* ARGSUSED */ +static double +ascii_hspan(const struct termp *p, const struct roffsu *su) +{ + double r; + + /* + * Approximate based on character width. These are generated + * entirely by eyeballing the screen, but appear to be correct. + */ + + switch (su->unit) { + case (SCALE_CM): + r = 4 * su->scale; + break; + case (SCALE_IN): + r = 10 * su->scale; + break; + case (SCALE_PC): + r = (10 * su->scale) / 6; + break; + case (SCALE_PT): + r = (10 * su->scale) / 72; + break; + case (SCALE_MM): + r = su->scale / 1000; + break; + case (SCALE_VS): + r = su->scale * 2 - 1; + break; + default: + r = su->scale; + break; + } + + return(r); +} + +#ifdef USE_WCHAR +/* ARGSUSED */ +static size_t +locale_width(const struct termp *p, int c) +{ + int rc; + + return((rc = wcwidth(c)) < 0 ? 0 : rc); +} + +/* ARGSUSED */ +static void +locale_advance(struct termp *p, size_t len) +{ + size_t i; + + for (i = 0; i < len; i++) + putwchar(L' '); +} + +/* ARGSUSED */ +static void +locale_endline(struct termp *p) +{ + + putwchar(L'\n'); +} + +/* ARGSUSED */ +static void +locale_letter(struct termp *p, int c) +{ + + putwchar(c); +} +#endif diff --git a/term_ps.c b/term_ps.c new file mode 100644 index 000000000000..e8a906858a6e --- /dev/null +++ b/term_ps.c @@ -0,0 +1,1185 @@ +/* $Id: term_ps.c,v 1.54 2011/10/16 12:20:34 schwarze Exp $ */ +/* + * Copyright (c) 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/types.h> + +#include <assert.h> +#include <stdarg.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "mandoc.h" +#include "out.h" +#include "main.h" +#include "term.h" + +/* These work the buffer used by the header and footer. */ +#define PS_BUFSLOP 128 + +/* Convert PostScript point "x" to an AFM unit. */ +#define PNT2AFM(p, x) /* LINTED */ \ + (size_t)((double)(x) * (1000.0 / (double)(p)->ps->scale)) + +/* Convert an AFM unit "x" to a PostScript points */ +#define AFM2PNT(p, x) /* LINTED */ \ + ((double)(x) / (1000.0 / (double)(p)->ps->scale)) + +struct glyph { + unsigned short wx; /* WX in AFM */ +}; + +struct font { + const char *name; /* FontName in AFM */ +#define MAXCHAR 95 /* total characters we can handle */ + struct glyph gly[MAXCHAR]; /* glyph metrics */ +}; + +struct termp_ps { + int flags; +#define PS_INLINE (1 << 0) /* we're in a word */ +#define PS_MARGINS (1 << 1) /* we're in the margins */ +#define PS_NEWPAGE (1 << 2) /* new page, no words yet */ + size_t pscol; /* visible column (AFM units) */ + size_t psrow; /* visible row (AFM units) */ + char *psmarg; /* margin buf */ + size_t psmargsz; /* margin buf size */ + size_t psmargcur; /* cur index in margin buf */ + char last; /* character buffer */ + enum termfont lastf; /* last set font */ + size_t scale; /* font scaling factor */ + size_t pages; /* number of pages shown */ + size_t lineheight; /* line height (AFM units) */ + size_t top; /* body top (AFM units) */ + size_t bottom; /* body bottom (AFM units) */ + size_t height; /* page height (AFM units */ + size_t width; /* page width (AFM units) */ + size_t left; /* body left (AFM units) */ + size_t header; /* header pos (AFM units) */ + size_t footer; /* footer pos (AFM units) */ + size_t pdfbytes; /* current output byte */ + size_t pdflastpg; /* byte of last page mark */ + size_t pdfbody; /* start of body object */ + size_t *pdfobjs; /* table of object offsets */ + size_t pdfobjsz; /* size of pdfobjs */ +}; + +static double ps_hspan(const struct termp *, + const struct roffsu *); +static size_t ps_width(const struct termp *, int); +static void ps_advance(struct termp *, size_t); +static void ps_begin(struct termp *); +static void ps_closepage(struct termp *); +static void ps_end(struct termp *); +static void ps_endline(struct termp *); +static void ps_fclose(struct termp *); +static void ps_growbuf(struct termp *, size_t); +static void ps_letter(struct termp *, int); +static void ps_pclose(struct termp *); +static void ps_pletter(struct termp *, int); +static void ps_printf(struct termp *, const char *, ...); +static void ps_putchar(struct termp *, char); +static void ps_setfont(struct termp *, enum termfont); +static struct termp *pspdf_alloc(char *); +static void pdf_obj(struct termp *, size_t); + +/* + * We define, for the time being, three fonts: bold, oblique/italic, and + * normal (roman). The following table hard-codes the font metrics for + * ASCII, i.e., 32--127. + */ + +static const struct font fonts[TERMFONT__MAX] = { + { "Times-Roman", { + { 250 }, + { 333 }, + { 408 }, + { 500 }, + { 500 }, + { 833 }, + { 778 }, + { 333 }, + { 333 }, + { 333 }, + { 500 }, + { 564 }, + { 250 }, + { 333 }, + { 250 }, + { 278 }, + { 500 }, + { 500 }, + { 500 }, + { 500 }, + { 500 }, + { 500 }, + { 500 }, + { 500 }, + { 500 }, + { 500 }, + { 278 }, + { 278 }, + { 564 }, + { 564 }, + { 564 }, + { 444 }, + { 921 }, + { 722 }, + { 667 }, + { 667 }, + { 722 }, + { 611 }, + { 556 }, + { 722 }, + { 722 }, + { 333 }, + { 389 }, + { 722 }, + { 611 }, + { 889 }, + { 722 }, + { 722 }, + { 556 }, + { 722 }, + { 667 }, + { 556 }, + { 611 }, + { 722 }, + { 722 }, + { 944 }, + { 722 }, + { 722 }, + { 611 }, + { 333 }, + { 278 }, + { 333 }, + { 469 }, + { 500 }, + { 333 }, + { 444 }, + { 500 }, + { 444 }, + { 500}, + { 444}, + { 333}, + { 500}, + { 500}, + { 278}, + { 278}, + { 500}, + { 278}, + { 778}, + { 500}, + { 500}, + { 500}, + { 500}, + { 333}, + { 389}, + { 278}, + { 500}, + { 500}, + { 722}, + { 500}, + { 500}, + { 444}, + { 480}, + { 200}, + { 480}, + { 541}, + } }, + { "Times-Bold", { + { 250 }, + { 333 }, + { 555 }, + { 500 }, + { 500 }, + { 1000 }, + { 833 }, + { 333 }, + { 333 }, + { 333 }, + { 500 }, + { 570 }, + { 250 }, + { 333 }, + { 250 }, + { 278 }, + { 500 }, + { 500 }, + { 500 }, + { 500 }, + { 500 }, + { 500 }, + { 500 }, + { 500 }, + { 500 }, + { 500 }, + { 333 }, + { 333 }, + { 570 }, + { 570 }, + { 570 }, + { 500 }, + { 930 }, + { 722 }, + { 667 }, + { 722 }, + { 722 }, + { 667 }, + { 611 }, + { 778 }, + { 778 }, + { 389 }, + { 500 }, + { 778 }, + { 667 }, + { 944 }, + { 722 }, + { 778 }, + { 611 }, + { 778 }, + { 722 }, + { 556 }, + { 667 }, + { 722 }, + { 722 }, + { 1000 }, + { 722 }, + { 722 }, + { 667 }, + { 333 }, + { 278 }, + { 333 }, + { 581 }, + { 500 }, + { 333 }, + { 500 }, + { 556 }, + { 444 }, + { 556 }, + { 444 }, + { 333 }, + { 500 }, + { 556 }, + { 278 }, + { 333 }, + { 556 }, + { 278 }, + { 833 }, + { 556 }, + { 500 }, + { 556 }, + { 556 }, + { 444 }, + { 389 }, + { 333 }, + { 556 }, + { 500 }, + { 722 }, + { 500 }, + { 500 }, + { 444 }, + { 394 }, + { 220 }, + { 394 }, + { 520 }, + } }, + { "Times-Italic", { + { 250 }, + { 333 }, + { 420 }, + { 500 }, + { 500 }, + { 833 }, + { 778 }, + { 333 }, + { 333 }, + { 333 }, + { 500 }, + { 675 }, + { 250 }, + { 333 }, + { 250 }, + { 278 }, + { 500 }, + { 500 }, + { 500 }, + { 500 }, + { 500 }, + { 500 }, + { 500 }, + { 500 }, + { 500 }, + { 500 }, + { 333 }, + { 333 }, + { 675 }, + { 675 }, + { 675 }, + { 500 }, + { 920 }, + { 611 }, + { 611 }, + { 667 }, + { 722 }, + { 611 }, + { 611 }, + { 722 }, + { 722 }, + { 333 }, + { 444 }, + { 667 }, + { 556 }, + { 833 }, + { 667 }, + { 722 }, + { 611 }, + { 722 }, + { 611 }, + { 500 }, + { 556 }, + { 722 }, + { 611 }, + { 833 }, + { 611 }, + { 556 }, + { 556 }, + { 389 }, + { 278 }, + { 389 }, + { 422 }, + { 500 }, + { 333 }, + { 500 }, + { 500 }, + { 444 }, + { 500 }, + { 444 }, + { 278 }, + { 500 }, + { 500 }, + { 278 }, + { 278 }, + { 444 }, + { 278 }, + { 722 }, + { 500 }, + { 500 }, + { 500 }, + { 500 }, + { 389 }, + { 389 }, + { 278 }, + { 500 }, + { 444 }, + { 667 }, + { 444 }, + { 444 }, + { 389 }, + { 400 }, + { 275 }, + { 400 }, + { 541 }, + } }, +}; + +void * +pdf_alloc(char *outopts) +{ + struct termp *p; + + if (NULL != (p = pspdf_alloc(outopts))) + p->type = TERMTYPE_PDF; + + return(p); +} + +void * +ps_alloc(char *outopts) +{ + struct termp *p; + + if (NULL != (p = pspdf_alloc(outopts))) + p->type = TERMTYPE_PS; + + return(p); +} + +static struct termp * +pspdf_alloc(char *outopts) +{ + struct termp *p; + unsigned int pagex, pagey; + size_t marginx, marginy, lineheight; + const char *toks[2]; + const char *pp; + char *v; + + p = mandoc_calloc(1, sizeof(struct termp)); + p->enc = TERMENC_ASCII; + p->ps = mandoc_calloc(1, sizeof(struct termp_ps)); + + p->advance = ps_advance; + p->begin = ps_begin; + p->end = ps_end; + p->endline = ps_endline; + p->hspan = ps_hspan; + p->letter = ps_letter; + p->width = ps_width; + + toks[0] = "paper"; + toks[1] = NULL; + + pp = NULL; + + while (outopts && *outopts) + switch (getsubopt(&outopts, UNCONST(toks), &v)) { + case (0): + pp = v; + break; + default: + break; + } + + /* Default to US letter (millimetres). */ + + pagex = 216; + pagey = 279; + + /* + * The ISO-269 paper sizes can be calculated automatically, but + * it would require bringing in -lm for pow() and I'd rather not + * do that. So just do it the easy way for now. Since this + * only happens once, I'm not terribly concerned. + */ + + if (pp && strcasecmp(pp, "letter")) { + if (0 == strcasecmp(pp, "a3")) { + pagex = 297; + pagey = 420; + } else if (0 == strcasecmp(pp, "a4")) { + pagex = 210; + pagey = 297; + } else if (0 == strcasecmp(pp, "a5")) { + pagex = 148; + pagey = 210; + } else if (0 == strcasecmp(pp, "legal")) { + pagex = 216; + pagey = 356; + } else if (2 != sscanf(pp, "%ux%u", &pagex, &pagey)) + fprintf(stderr, "%s: Unknown paper\n", pp); + } + + /* + * This MUST be defined before any PNT2AFM or AFM2PNT + * calculations occur. + */ + + p->ps->scale = 11; + + /* Remember millimetres -> AFM units. */ + + pagex = PNT2AFM(p, ((double)pagex * 2.834)); + pagey = PNT2AFM(p, ((double)pagey * 2.834)); + + /* Margins are 1/9 the page x and y. */ + + marginx = /* LINTED */ + (size_t)((double)pagex / 9.0); + marginy = /* LINTED */ + (size_t)((double)pagey / 9.0); + + /* Line-height is 1.4em. */ + + lineheight = PNT2AFM(p, ((double)p->ps->scale * 1.4)); + + p->ps->width = (size_t)pagex; + p->ps->height = (size_t)pagey; + p->ps->header = pagey - (marginy / 2) - (lineheight / 2); + p->ps->top = pagey - marginy; + p->ps->footer = (marginy / 2) - (lineheight / 2); + p->ps->bottom = marginy; + p->ps->left = marginx; + p->ps->lineheight = lineheight; + + p->defrmargin = pagex - (marginx * 2); + return(p); +} + + +void +pspdf_free(void *arg) +{ + struct termp *p; + + p = (struct termp *)arg; + + if (p->ps->psmarg) + free(p->ps->psmarg); + if (p->ps->pdfobjs) + free(p->ps->pdfobjs); + + free(p->ps); + term_free(p); +} + + +static void +ps_printf(struct termp *p, const char *fmt, ...) +{ + va_list ap; + int pos, len; + + va_start(ap, fmt); + + /* + * If we're running in regular mode, then pipe directly into + * vprintf(). If we're processing margins, then push the data + * into our growable margin buffer. + */ + + if ( ! (PS_MARGINS & p->ps->flags)) { + len = vprintf(fmt, ap); + va_end(ap); + p->ps->pdfbytes += /* LINTED */ + len < 0 ? 0 : (size_t)len; + return; + } + + /* + * XXX: I assume that the in-margin print won't exceed + * PS_BUFSLOP (128 bytes), which is reasonable but still an + * assumption that will cause pukeage if it's not the case. + */ + + ps_growbuf(p, PS_BUFSLOP); + + pos = (int)p->ps->psmargcur; + vsnprintf(&p->ps->psmarg[pos], PS_BUFSLOP, fmt, ap); + + va_end(ap); + + p->ps->psmargcur = strlen(p->ps->psmarg); +} + + +static void +ps_putchar(struct termp *p, char c) +{ + int pos; + + /* See ps_printf(). */ + + if ( ! (PS_MARGINS & p->ps->flags)) { + /* LINTED */ + putchar(c); + p->ps->pdfbytes++; + return; + } + + ps_growbuf(p, 2); + + pos = (int)p->ps->psmargcur++; + p->ps->psmarg[pos++] = c; + p->ps->psmarg[pos] = '\0'; +} + + +static void +pdf_obj(struct termp *p, size_t obj) +{ + + assert(obj > 0); + + if ((obj - 1) >= p->ps->pdfobjsz) { + p->ps->pdfobjsz = obj + 128; + p->ps->pdfobjs = realloc + (p->ps->pdfobjs, + p->ps->pdfobjsz * sizeof(size_t)); + if (NULL == p->ps->pdfobjs) { + perror(NULL); + exit((int)MANDOCLEVEL_SYSERR); + } + } + + p->ps->pdfobjs[(int)obj - 1] = p->ps->pdfbytes; + ps_printf(p, "%zu 0 obj\n", obj); +} + + +static void +ps_closepage(struct termp *p) +{ + int i; + size_t len, base; + + /* + * Close out a page that we've already flushed to output. In + * PostScript, we simply note that the page must be showed. In + * PDF, we must now create the Length, Resource, and Page node + * for the page contents. + */ + + assert(p->ps->psmarg && p->ps->psmarg[0]); + ps_printf(p, "%s", p->ps->psmarg); + + if (TERMTYPE_PS != p->type) { + ps_printf(p, "ET\n"); + + len = p->ps->pdfbytes - p->ps->pdflastpg; + base = p->ps->pages * 4 + p->ps->pdfbody; + + ps_printf(p, "endstream\nendobj\n"); + + /* Length of content. */ + pdf_obj(p, base + 1); + ps_printf(p, "%zu\nendobj\n", len); + + /* Resource for content. */ + pdf_obj(p, base + 2); + ps_printf(p, "<<\n/ProcSet [/PDF /Text]\n"); + ps_printf(p, "/Font <<\n"); + for (i = 0; i < (int)TERMFONT__MAX; i++) + ps_printf(p, "/F%d %d 0 R\n", i, 3 + i); + ps_printf(p, ">>\n>>\n"); + + /* Page node. */ + pdf_obj(p, base + 3); + ps_printf(p, "<<\n"); + ps_printf(p, "/Type /Page\n"); + ps_printf(p, "/Parent 2 0 R\n"); + ps_printf(p, "/Resources %zu 0 R\n", base + 2); + ps_printf(p, "/Contents %zu 0 R\n", base); + ps_printf(p, ">>\nendobj\n"); + } else + ps_printf(p, "showpage\n"); + + p->ps->pages++; + p->ps->psrow = p->ps->top; + assert( ! (PS_NEWPAGE & p->ps->flags)); + p->ps->flags |= PS_NEWPAGE; +} + + +/* ARGSUSED */ +static void +ps_end(struct termp *p) +{ + size_t i, xref, base; + + /* + * At the end of the file, do one last showpage. This is the + * same behaviour as groff(1) and works for multiple pages as + * well as just one. + */ + + if ( ! (PS_NEWPAGE & p->ps->flags)) { + assert(0 == p->ps->flags); + assert('\0' == p->ps->last); + ps_closepage(p); + } + + if (TERMTYPE_PS == p->type) { + ps_printf(p, "%%%%Trailer\n"); + ps_printf(p, "%%%%Pages: %zu\n", p->ps->pages); + ps_printf(p, "%%%%EOF\n"); + return; + } + + pdf_obj(p, 2); + ps_printf(p, "<<\n/Type /Pages\n"); + ps_printf(p, "/MediaBox [0 0 %zu %zu]\n", + (size_t)AFM2PNT(p, p->ps->width), + (size_t)AFM2PNT(p, p->ps->height)); + + ps_printf(p, "/Count %zu\n", p->ps->pages); + ps_printf(p, "/Kids ["); + + for (i = 0; i < p->ps->pages; i++) + ps_printf(p, " %zu 0 R", i * 4 + + p->ps->pdfbody + 3); + + base = (p->ps->pages - 1) * 4 + + p->ps->pdfbody + 4; + + ps_printf(p, "]\n>>\nendobj\n"); + pdf_obj(p, base); + ps_printf(p, "<<\n"); + ps_printf(p, "/Type /Catalog\n"); + ps_printf(p, "/Pages 2 0 R\n"); + ps_printf(p, ">>\n"); + xref = p->ps->pdfbytes; + ps_printf(p, "xref\n"); + ps_printf(p, "0 %zu\n", base + 1); + ps_printf(p, "0000000000 65535 f \n"); + + for (i = 0; i < base; i++) + ps_printf(p, "%.10zu 00000 n \n", + p->ps->pdfobjs[(int)i]); + + ps_printf(p, "trailer\n"); + ps_printf(p, "<<\n"); + ps_printf(p, "/Size %zu\n", base + 1); + ps_printf(p, "/Root %zu 0 R\n", base); + ps_printf(p, "/Info 1 0 R\n"); + ps_printf(p, ">>\n"); + ps_printf(p, "startxref\n"); + ps_printf(p, "%zu\n", xref); + ps_printf(p, "%%%%EOF\n"); +} + + +static void +ps_begin(struct termp *p) +{ + time_t t; + int i; + + /* + * Print margins into margin buffer. Nothing gets output to the + * screen yet, so we don't need to initialise the primary state. + */ + + if (p->ps->psmarg) { + assert(p->ps->psmargsz); + p->ps->psmarg[0] = '\0'; + } + + /*p->ps->pdfbytes = 0;*/ + p->ps->psmargcur = 0; + p->ps->flags = PS_MARGINS; + p->ps->pscol = p->ps->left; + p->ps->psrow = p->ps->header; + + ps_setfont(p, TERMFONT_NONE); + + (*p->headf)(p, p->argf); + (*p->endline)(p); + + p->ps->pscol = p->ps->left; + p->ps->psrow = p->ps->footer; + + (*p->footf)(p, p->argf); + (*p->endline)(p); + + p->ps->flags &= ~PS_MARGINS; + + assert(0 == p->ps->flags); + assert(p->ps->psmarg); + assert('\0' != p->ps->psmarg[0]); + + /* + * Print header and initialise page state. Following this, + * stuff gets printed to the screen, so make sure we're sane. + */ + + t = time(NULL); + + if (TERMTYPE_PS == p->type) { + ps_printf(p, "%%!PS-Adobe-3.0\n"); + ps_printf(p, "%%%%CreationDate: %s", ctime(&t)); + ps_printf(p, "%%%%DocumentData: Clean7Bit\n"); + ps_printf(p, "%%%%Orientation: Portrait\n"); + ps_printf(p, "%%%%Pages: (atend)\n"); + ps_printf(p, "%%%%PageOrder: Ascend\n"); + ps_printf(p, "%%%%DocumentMedia: " + "Default %zu %zu 0 () ()\n", + (size_t)AFM2PNT(p, p->ps->width), + (size_t)AFM2PNT(p, p->ps->height)); + ps_printf(p, "%%%%DocumentNeededResources: font"); + + for (i = 0; i < (int)TERMFONT__MAX; i++) + ps_printf(p, " %s", fonts[i].name); + + ps_printf(p, "\n%%%%EndComments\n"); + } else { + ps_printf(p, "%%PDF-1.1\n"); + pdf_obj(p, 1); + ps_printf(p, "<<\n"); + ps_printf(p, ">>\n"); + ps_printf(p, "endobj\n"); + + for (i = 0; i < (int)TERMFONT__MAX; i++) { + pdf_obj(p, (size_t)i + 3); + ps_printf(p, "<<\n"); + ps_printf(p, "/Type /Font\n"); + ps_printf(p, "/Subtype /Type1\n"); + ps_printf(p, "/Name /F%zu\n", i); + ps_printf(p, "/BaseFont /%s\n", fonts[i].name); + ps_printf(p, ">>\n"); + } + } + + p->ps->pdfbody = (size_t)TERMFONT__MAX + 3; + p->ps->pscol = p->ps->left; + p->ps->psrow = p->ps->top; + p->ps->flags |= PS_NEWPAGE; + ps_setfont(p, TERMFONT_NONE); +} + + +static void +ps_pletter(struct termp *p, int c) +{ + int f; + + /* + * If we haven't opened a page context, then output that we're + * in a new page and make sure the font is correctly set. + */ + + if (PS_NEWPAGE & p->ps->flags) { + if (TERMTYPE_PS == p->type) { + ps_printf(p, "%%%%Page: %zu %zu\n", + p->ps->pages + 1, + p->ps->pages + 1); + ps_printf(p, "/%s %zu selectfont\n", + fonts[(int)p->ps->lastf].name, + p->ps->scale); + } else { + pdf_obj(p, p->ps->pdfbody + + p->ps->pages * 4); + ps_printf(p, "<<\n"); + ps_printf(p, "/Length %zu 0 R\n", + p->ps->pdfbody + 1 + + p->ps->pages * 4); + ps_printf(p, ">>\nstream\n"); + } + p->ps->pdflastpg = p->ps->pdfbytes; + p->ps->flags &= ~PS_NEWPAGE; + } + + /* + * If we're not in a PostScript "word" context, then open one + * now at the current cursor. + */ + + if ( ! (PS_INLINE & p->ps->flags)) { + if (TERMTYPE_PS != p->type) { + ps_printf(p, "BT\n/F%d %zu Tf\n", + (int)p->ps->lastf, + p->ps->scale); + ps_printf(p, "%.3f %.3f Td\n(", + AFM2PNT(p, p->ps->pscol), + AFM2PNT(p, p->ps->psrow)); + } else + ps_printf(p, "%.3f %.3f moveto\n(", + AFM2PNT(p, p->ps->pscol), + AFM2PNT(p, p->ps->psrow)); + p->ps->flags |= PS_INLINE; + } + + assert( ! (PS_NEWPAGE & p->ps->flags)); + + /* + * We need to escape these characters as per the PostScript + * specification. We would also escape non-graphable characters + * (like tabs), but none of them would get to this point and + * it's superfluous to abort() on them. + */ + + switch (c) { + case ('('): + /* FALLTHROUGH */ + case (')'): + /* FALLTHROUGH */ + case ('\\'): + ps_putchar(p, '\\'); + break; + default: + break; + } + + /* Write the character and adjust where we are on the page. */ + + f = (int)p->ps->lastf; + + if (c <= 32 || (c - 32 >= MAXCHAR)) { + ps_putchar(p, ' '); + p->ps->pscol += (size_t)fonts[f].gly[0].wx; + return; + } + + ps_putchar(p, (char)c); + c -= 32; + p->ps->pscol += (size_t)fonts[f].gly[c].wx; +} + + +static void +ps_pclose(struct termp *p) +{ + + /* + * Spit out that we're exiting a word context (this is a + * "partial close" because we don't check the last-char buffer + * or anything). + */ + + if ( ! (PS_INLINE & p->ps->flags)) + return; + + if (TERMTYPE_PS != p->type) { + ps_printf(p, ") Tj\nET\n"); + } else + ps_printf(p, ") show\n"); + + p->ps->flags &= ~PS_INLINE; +} + + +static void +ps_fclose(struct termp *p) +{ + + /* + * Strong closure: if we have a last-char, spit it out after + * checking that we're in the right font mode. This will of + * course open a new scope, if applicable. + * + * Following this, close out any scope that's open. + */ + + if ('\0' != p->ps->last) { + if (p->ps->lastf != TERMFONT_NONE) { + ps_pclose(p); + ps_setfont(p, TERMFONT_NONE); + } + ps_pletter(p, p->ps->last); + p->ps->last = '\0'; + } + + if ( ! (PS_INLINE & p->ps->flags)) + return; + + ps_pclose(p); +} + + +static void +ps_letter(struct termp *p, int arg) +{ + char cc, c; + + /* LINTED */ + c = arg >= 128 || arg <= 0 ? '?' : arg; + + /* + * State machine dictates whether to buffer the last character + * or not. Basically, encoded words are detected by checking if + * we're an "8" and switching on the buffer. Then we put "8" in + * our buffer, and on the next charater, flush both character + * and buffer. Thus, "regular" words are detected by having a + * regular character and a regular buffer character. + */ + + if ('\0' == p->ps->last) { + assert(8 != c); + p->ps->last = c; + return; + } else if (8 == p->ps->last) { + assert(8 != c); + p->ps->last = '\0'; + } else if (8 == c) { + assert(8 != p->ps->last); + if ('_' == p->ps->last) { + if (p->ps->lastf != TERMFONT_UNDER) { + ps_pclose(p); + ps_setfont(p, TERMFONT_UNDER); + } + } else if (p->ps->lastf != TERMFONT_BOLD) { + ps_pclose(p); + ps_setfont(p, TERMFONT_BOLD); + } + p->ps->last = c; + return; + } else { + if (p->ps->lastf != TERMFONT_NONE) { + ps_pclose(p); + ps_setfont(p, TERMFONT_NONE); + } + cc = p->ps->last; + p->ps->last = c; + c = cc; + } + + ps_pletter(p, c); +} + + +static void +ps_advance(struct termp *p, size_t len) +{ + + /* + * Advance some spaces. This can probably be made smarter, + * i.e., to have multiple space-separated words in the same + * scope, but this is easier: just close out the current scope + * and readjust our column settings. + */ + + ps_fclose(p); + p->ps->pscol += len; +} + + +static void +ps_endline(struct termp *p) +{ + + /* Close out any scopes we have open: we're at eoln. */ + + ps_fclose(p); + + /* + * If we're in the margin, don't try to recalculate our current + * row. XXX: if the column tries to be fancy with multiple + * lines, we'll do nasty stuff. + */ + + if (PS_MARGINS & p->ps->flags) + return; + + /* Left-justify. */ + + p->ps->pscol = p->ps->left; + + /* If we haven't printed anything, return. */ + + if (PS_NEWPAGE & p->ps->flags) + return; + + /* + * Put us down a line. If we're at the page bottom, spit out a + * showpage and restart our row. + */ + + if (p->ps->psrow >= p->ps->lineheight + + p->ps->bottom) { + p->ps->psrow -= p->ps->lineheight; + return; + } + + ps_closepage(p); +} + + +static void +ps_setfont(struct termp *p, enum termfont f) +{ + + assert(f < TERMFONT__MAX); + p->ps->lastf = f; + + /* + * If we're still at the top of the page, let the font-setting + * be delayed until we actually have stuff to print. + */ + + if (PS_NEWPAGE & p->ps->flags) + return; + + if (TERMTYPE_PS == p->type) + ps_printf(p, "/%s %zu selectfont\n", + fonts[(int)f].name, + p->ps->scale); + else + ps_printf(p, "/F%d %zu Tf\n", + (int)f, + p->ps->scale); +} + + +/* ARGSUSED */ +static size_t +ps_width(const struct termp *p, int c) +{ + + if (c <= 32 || c - 32 >= MAXCHAR) + return((size_t)fonts[(int)TERMFONT_NONE].gly[0].wx); + + c -= 32; + return((size_t)fonts[(int)TERMFONT_NONE].gly[c].wx); +} + + +static double +ps_hspan(const struct termp *p, const struct roffsu *su) +{ + double r; + + /* + * All of these measurements are derived by converting from the + * native measurement to AFM units. + */ + + switch (su->unit) { + case (SCALE_CM): + r = PNT2AFM(p, su->scale * 28.34); + break; + case (SCALE_IN): + r = PNT2AFM(p, su->scale * 72); + break; + case (SCALE_PC): + r = PNT2AFM(p, su->scale * 12); + break; + case (SCALE_PT): + r = PNT2AFM(p, su->scale * 100); + break; + case (SCALE_EM): + r = su->scale * + fonts[(int)TERMFONT_NONE].gly[109 - 32].wx; + break; + case (SCALE_MM): + r = PNT2AFM(p, su->scale * 2.834); + break; + case (SCALE_EN): + r = su->scale * + fonts[(int)TERMFONT_NONE].gly[110 - 32].wx; + break; + case (SCALE_VS): + r = su->scale * p->ps->lineheight; + break; + default: + r = su->scale; + break; + } + + return(r); +} + +static void +ps_growbuf(struct termp *p, size_t sz) +{ + if (p->ps->psmargcur + sz <= p->ps->psmargsz) + return; + + if (sz < PS_BUFSLOP) + sz = PS_BUFSLOP; + + p->ps->psmargsz += sz; + + p->ps->psmarg = mandoc_realloc + (p->ps->psmarg, p->ps->psmargsz); +} + diff --git a/test-fgetln.c b/test-fgetln.c new file mode 100644 index 000000000000..90869cd89fd7 --- /dev/null +++ b/test-fgetln.c @@ -0,0 +1,11 @@ +#include <stdio.h> +#include <stdlib.h> + +int +main(void) +{ + char *cp; + size_t sz; + cp = fgetln(stdin, &sz); + return 0; +} diff --git a/test-getsubopt.c b/test-getsubopt.c new file mode 100644 index 000000000000..25e11246e67b --- /dev/null +++ b/test-getsubopt.c @@ -0,0 +1,12 @@ +#if defined(__linux__) || defined(__MINT__) +# define _GNU_SOURCE /* getsubopt() */ +#endif + +#include <stdlib.h> + +int +main(int argc, char **argv) +{ + getsubopt(argv, argv, argv); + return 0; +} diff --git a/test-mmap.c b/test-mmap.c new file mode 100644 index 000000000000..db8fd8732e4d --- /dev/null +++ b/test-mmap.c @@ -0,0 +1,10 @@ +#include <sys/types.h> +#include <sys/mman.h> + +int +main(int argc, char **argv) +{ + + mmap(0, 0, PROT_READ, MAP_FILE|MAP_SHARED, -1, 0); + return 0; +} diff --git a/test-strlcat.c b/test-strlcat.c new file mode 100644 index 000000000000..5d450dd04d4e --- /dev/null +++ b/test-strlcat.c @@ -0,0 +1,8 @@ +#include <string.h> + +int +main(int argc, char **argv) +{ + strlcat(argv[0], argv[1], 10); + return 0; +} diff --git a/test-strlcpy.c b/test-strlcpy.c new file mode 100644 index 000000000000..c7d182aaf27c --- /dev/null +++ b/test-strlcpy.c @@ -0,0 +1,8 @@ +#include <string.h> + +int +main(int argc, char **argv) +{ + strlcpy(argv[0], argv[1], 10); + return 0; +} diff --git a/test-strptime.c b/test-strptime.c new file mode 100644 index 000000000000..976e9c8066a3 --- /dev/null +++ b/test-strptime.c @@ -0,0 +1,13 @@ +#if defined(__linux__) || defined(__MINT__) +# define _GNU_SOURCE /* strptime(), getsubopt() */ +#endif + +#include <time.h> + +int +main(int argc, char **argv) +{ + struct tm tm; + strptime(*argv, "%D", &tm); + return 0; +} diff --git a/tree.c b/tree.c new file mode 100644 index 000000000000..1430c737e00e --- /dev/null +++ b/tree.c @@ -0,0 +1,349 @@ +/* $Id: tree.c,v 1.47 2011/09/18 14:14:15 schwarze Exp $ */ +/* + * Copyright (c) 2008, 2009, 2011 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <assert.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> + +#include "mandoc.h" +#include "mdoc.h" +#include "man.h" +#include "main.h" + +static void print_box(const struct eqn_box *, int); +static void print_man(const struct man_node *, int); +static void print_mdoc(const struct mdoc_node *, int); +static void print_span(const struct tbl_span *, int); + + +/* ARGSUSED */ +void +tree_mdoc(void *arg, const struct mdoc *mdoc) +{ + + print_mdoc(mdoc_node(mdoc), 0); +} + + +/* ARGSUSED */ +void +tree_man(void *arg, const struct man *man) +{ + + print_man(man_node(man), 0); +} + + +static void +print_mdoc(const struct mdoc_node *n, int indent) +{ + const char *p, *t; + int i, j; + size_t argc, sz; + char **params; + struct mdoc_argv *argv; + + argv = NULL; + argc = sz = 0; + params = NULL; + t = p = NULL; + + switch (n->type) { + case (MDOC_ROOT): + t = "root"; + break; + case (MDOC_BLOCK): + t = "block"; + break; + case (MDOC_HEAD): + t = "block-head"; + break; + case (MDOC_BODY): + if (n->end) + t = "body-end"; + else + t = "block-body"; + break; + case (MDOC_TAIL): + t = "block-tail"; + break; + case (MDOC_ELEM): + t = "elem"; + break; + case (MDOC_TEXT): + t = "text"; + break; + case (MDOC_TBL): + /* FALLTHROUGH */ + case (MDOC_EQN): + break; + default: + abort(); + /* NOTREACHED */ + } + + switch (n->type) { + case (MDOC_TEXT): + p = n->string; + break; + case (MDOC_BODY): + p = mdoc_macronames[n->tok]; + break; + case (MDOC_HEAD): + p = mdoc_macronames[n->tok]; + break; + case (MDOC_TAIL): + p = mdoc_macronames[n->tok]; + break; + case (MDOC_ELEM): + p = mdoc_macronames[n->tok]; + if (n->args) { + argv = n->args->argv; + argc = n->args->argc; + } + break; + case (MDOC_BLOCK): + p = mdoc_macronames[n->tok]; + if (n->args) { + argv = n->args->argv; + argc = n->args->argc; + } + break; + case (MDOC_TBL): + /* FALLTHROUGH */ + case (MDOC_EQN): + break; + case (MDOC_ROOT): + p = "root"; + break; + default: + abort(); + /* NOTREACHED */ + } + + if (n->span) { + assert(NULL == p && NULL == t); + print_span(n->span, indent); + } else if (n->eqn) { + assert(NULL == p && NULL == t); + print_box(n->eqn->root, indent); + } else { + for (i = 0; i < indent; i++) + putchar('\t'); + + printf("%s (%s)", p, t); + + for (i = 0; i < (int)argc; i++) { + printf(" -%s", mdoc_argnames[argv[i].arg]); + if (argv[i].sz > 0) + printf(" ["); + for (j = 0; j < (int)argv[i].sz; j++) + printf(" [%s]", argv[i].value[j]); + if (argv[i].sz > 0) + printf(" ]"); + } + + for (i = 0; i < (int)sz; i++) + printf(" [%s]", params[i]); + + printf(" %d:%d\n", n->line, n->pos); + } + + if (n->child) + print_mdoc(n->child, indent + 1); + if (n->next) + print_mdoc(n->next, indent); +} + + +static void +print_man(const struct man_node *n, int indent) +{ + const char *p, *t; + int i; + + t = p = NULL; + + switch (n->type) { + case (MAN_ROOT): + t = "root"; + break; + case (MAN_ELEM): + t = "elem"; + break; + case (MAN_TEXT): + t = "text"; + break; + case (MAN_BLOCK): + t = "block"; + break; + case (MAN_HEAD): + t = "block-head"; + break; + case (MAN_BODY): + t = "block-body"; + break; + case (MAN_TAIL): + t = "block-tail"; + break; + case (MAN_TBL): + /* FALLTHROUGH */ + case (MAN_EQN): + break; + default: + abort(); + /* NOTREACHED */ + } + + switch (n->type) { + case (MAN_TEXT): + p = n->string; + break; + case (MAN_ELEM): + /* FALLTHROUGH */ + case (MAN_BLOCK): + /* FALLTHROUGH */ + case (MAN_HEAD): + /* FALLTHROUGH */ + case (MAN_TAIL): + /* FALLTHROUGH */ + case (MAN_BODY): + p = man_macronames[n->tok]; + break; + case (MAN_ROOT): + p = "root"; + break; + case (MAN_TBL): + /* FALLTHROUGH */ + case (MAN_EQN): + break; + default: + abort(); + /* NOTREACHED */ + } + + if (n->span) { + assert(NULL == p && NULL == t); + print_span(n->span, indent); + } else if (n->eqn) { + assert(NULL == p && NULL == t); + print_box(n->eqn->root, indent); + } else { + for (i = 0; i < indent; i++) + putchar('\t'); + printf("%s (%s) %d:%d\n", p, t, n->line, n->pos); + } + + if (n->child) + print_man(n->child, indent + 1); + if (n->next) + print_man(n->next, indent); +} + +static void +print_box(const struct eqn_box *ep, int indent) +{ + int i; + const char *t; + + if (NULL == ep) + return; + for (i = 0; i < indent; i++) + putchar('\t'); + + t = NULL; + switch (ep->type) { + case (EQN_ROOT): + t = "eqn-root"; + break; + case (EQN_LIST): + t = "eqn-list"; + break; + case (EQN_SUBEXPR): + t = "eqn-expr"; + break; + case (EQN_TEXT): + t = "eqn-text"; + break; + case (EQN_MATRIX): + t = "eqn-matrix"; + break; + } + + assert(t); + printf("%s(%d, %d, %d, %d, %d, \"%s\", \"%s\") %s\n", + t, EQN_DEFSIZE == ep->size ? 0 : ep->size, + ep->pos, ep->font, ep->mark, ep->pile, + ep->left ? ep->left : "", + ep->right ? ep->right : "", + ep->text ? ep->text : ""); + + print_box(ep->first, indent + 1); + print_box(ep->next, indent); +} + +static void +print_span(const struct tbl_span *sp, int indent) +{ + const struct tbl_dat *dp; + int i; + + for (i = 0; i < indent; i++) + putchar('\t'); + + switch (sp->pos) { + case (TBL_SPAN_HORIZ): + putchar('-'); + return; + case (TBL_SPAN_DHORIZ): + putchar('='); + return; + default: + break; + } + + for (dp = sp->first; dp; dp = dp->next) { + switch (dp->pos) { + case (TBL_DATA_HORIZ): + /* FALLTHROUGH */ + case (TBL_DATA_NHORIZ): + putchar('-'); + continue; + case (TBL_DATA_DHORIZ): + /* FALLTHROUGH */ + case (TBL_DATA_NDHORIZ): + putchar('='); + continue; + default: + break; + } + printf("[\"%s\"", dp->string ? dp->string : ""); + if (dp->spans) + printf("(%d)", dp->spans); + if (NULL == dp->layout) + putchar('*'); + putchar(']'); + putchar(' '); + } + + printf("(tbl) %d:1\n", sp->line); +} diff --git a/vol.c b/vol.c new file mode 100644 index 000000000000..3ea7441a4219 --- /dev/null +++ b/vol.c @@ -0,0 +1,39 @@ +/* $Id: vol.c,v 1.9 2011/03/22 14:33:05 kristaps Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "mdoc.h" +#include "mandoc.h" +#include "libmdoc.h" + +#define LINE(x, y) \ + if (0 == strcmp(p, x)) return(y); + +const char * +mdoc_a2vol(const char *p) +{ + +#include "vol.in" + + return(NULL); +} diff --git a/vol.in b/vol.in new file mode 100644 index 000000000000..7650b57a14d3 --- /dev/null +++ b/vol.in @@ -0,0 +1,35 @@ +/* $Id: vol.in,v 1.6 2010/06/19 20:46:28 kristaps Exp $ */ +/* + * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This file defines volume titles for .Dt. + * + * Be sure to escape strings. + */ + +LINE("USD", "User\'s Supplementary Documents") +LINE("PS1", "Programmer\'s Supplementary Documents") +LINE("AMD", "Ancestral Manual Documents") +LINE("SMM", "System Manager\'s Manual") +LINE("URM", "User\'s Reference Manual") +LINE("PRM", "Programmer\'s Manual") +LINE("KM", "Kernel Manual") +LINE("IND", "Manual Master Index") +LINE("MMI", "Manual Master Index") +LINE("LOCAL", "Local Manual") +LINE("LOC", "Local Manual") +LINE("CON", "Contributed Software Manual") diff --git a/whatis.1 b/whatis.1 new file mode 100644 index 000000000000..500b0b6c34d4 --- /dev/null +++ b/whatis.1 @@ -0,0 +1,190 @@ +.\" $Id: whatis.1,v 1.8 2012/03/24 01:54:43 kristaps Exp $ +.\" +.\" Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: March 24 2012 $ +.Dt WHATIS 1 +.Os +.Sh NAME +.Nm whatis +.Nd search for manual pages by page names +.Sh SYNOPSIS +.Nm +.Op Fl C Ar file +.Op Fl M Ar manpath +.Op Fl m Ar manpath +.Op Fl S Ar arch +.Op Fl s Ar section +.Ar name ... +.Sh DESCRIPTION +The +.Nm +utility searches databases generated by +.Xr mandocdb 8 +for manuals containing the word +.Ar name +in their page name, ignoring case. +It returns the header lines from all matching pages. +You can then use the +.Xr man 1 +command to get more information. +.Pp +By default, +.Nm +searches for +.Xr mandocdb 8 +databases in the default paths stipulated by +.Xr man 1 . +If standard output is a TTY, a result may be selected from a list and +its manual displayed with the pager. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl C Ar file +Specify an alternative configuration +.Ar file +in +.Xr man.conf 5 +format. +.It Fl M Ar manpath +Use the colon-separated +.Ar manpath +instead of the default list of paths searched for +.Xr mandocdb 8 +databases. +Invalid paths, or paths without manual databases, are ignored. +.It Fl m Ar manpath +Prepend the colon-separated +.Ar manpath +to the list of paths searched for +.Xr mandocdb 8 +databases. +Invalid paths, or paths without manual databases, are ignored. +.It Fl S Ar arch +Search only for a particular architecture. +.It Fl s Ar cat +Search only for a manual section. +See +.Xr man 1 +for a listing of manual sections. +.El +.Pp +Results are sorted by manual title, with output formatted as +.Pp +.D1 title(sec) \- description +.Pp +Where +.Qq title +is the manual's title (note multiple manual names may exist for one +title), +.Qq sec +is the manual section, and +.Qq description +is the manual's short description. +If an architecture is specified for the manual, it is displayed as +.Pp +.D1 title(cat/arch) \- description +.Pp +If on a TTY, results are prefixed with a numeric identifier. +.Pp +.D1 [index] title(cat) \- description +.Pp +One may choose a manual be entering the index at the prompt. +Valid choices are displayed using +.Ev MANPAGER , +or failing that , +.Ev PAGER +or just +.Xr more 1 . +Source pages are formatted with +.Xr mandoc 1 ; +preformatted pages with +.Xr cat 1 . +.Pp +.Nm +is identical to running +.Xr apropos 1 +as follows: +.Pp +.Dl $ apropos -- -i 'Nm~[[:<:]]term[[:>:]]' +.Sh ENVIRONMENT +.Bl -tag -width Ds +.It Ev MANPAGER +Default pager for manuals. +If this is unset, falls back to +.Ev Pager . +.It Ev PAGER +The second choice for a manual pager. +If this is unset, use +.Xr more 1 . +.It Ev MANPATH +Colon-separated paths modifying the default list of paths searched for +manual databases. +Invalid paths, or paths without manual databases, are ignored. +Overridden by +.Fl M . +If +.Ev MANPATH +begins with a +.Sq \&: , +it is appended to the default list; +else if it ends with +.Sq \&: , +it is prepended to the default list; else if it contains +.Sq \&:: , +the default list is inserted between the colons. +If none of these conditions are met, it overrides the default list. +.El +.Sh FILES +.Bl -tag -width "/etc/man.conf" -compact +.It Pa whatis.db +name of the +.Xr mandocdb 8 +keyword database +.It Pa whatis.index +name of the +.Xr mandocdb 8 +filename database +.It Pa /etc/man.conf +default +.Xr man 1 +configuration file +.El +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr apropos 1 , +.Xr man 1 , +.Xr man.conf 5 , +.Xr mandocdb 8 +.Sh HISTORY +The +.Nm +utility first appeared in +.Bx 2 . +It was rewritten from scratch as part of the +.Xr mandocdb 8 +project for +.Ox 5.1 . +.Sh AUTHORS +.An -nosplit +.An Bill Joy +wrote the original +.Bx +.Nm +in 1979. +The current version was written by +.An Kristaps Dzonsons , +.Mt kristaps@bsd.lv . |