xref: /plan9-contrib/sys/src/cmd/ip/httpd/man2html.c (revision 7dd7cddf99dd7472612f1413b4da293630e6b1bc)
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include "httpd.h"
5 
6 enum
7 {
8 	SSIZE = 10,
9 
10 	/* list types */
11 	Lordered = 1,
12 	Lunordered,
13 	Ldef,
14 	Lother,
15 
16 };
17 
18 static	Biobuf	bin;
19 static	Hio	houtb;
20 static	Hio	*hout;
21 static	Connect	*connect;
22 static	char	section[32];
23 static	char	onelinefont[32];
24 
25 static	Biobuf in;
26 static	int sol;
27 static	char	me[] = "/magic/man2html/";
28 
29 static	int list, listnum, indents, example, hangingdt;
30 
31 typedef struct Goobie	Goobie;
32 struct Goobie
33 {
34 	char *name;
35 	void (*f)(int, char**);
36 };
37 
38 typedef void F(int, char**);
39 F g_1C, g_2C, g_B, g_BI, g_BR, g_DT, g_EE, g_EX, g_HP, g_I;
40 F g_IB, g_IP, g_IR, g_L, g_LP, g_LR, g_PD, g_PP, g_RB, g_RE, g_RI;
41 F g_RL, g_RS, g_SH, g_SM, g_SS, g_TF, g_TH, g_TP, g_br, g_ft, g_nf, g_fi;
42 
43 F g_notyet, g_ignore;
44 
45 static Goobie gtab[] =
46 {
47 	{ "1C",	g_ignore, },
48 	{ "2C",	g_ignore, },
49 	{ "B",	g_B, },
50 	{ "BI",	g_BI, },
51 	{ "BR",	g_BR, },
52 	{ "DT",	g_ignore, },
53 	{ "EE",	g_EE, },
54 	{ "EX",	g_EX, },
55 	{ "HP",	g_HP, },
56 	{ "I",	g_I, },
57 	{ "IB",	g_IB, },
58 	{ "IP",	g_IP, },
59 	{ "IR",	g_IR, },
60 	{ "L",	g_L, },
61 	{ "LP",	g_LP, },
62 	{ "LR",	g_LR, },
63 	{ "PD",	g_ignore, },
64 	{ "PP",	g_PP, },
65 	{ "RB",	g_RB, },
66 	{ "RE",	g_RE, },
67 	{ "RI",	g_RI, },
68 	{ "RL",	g_RL, },
69 	{ "RS",	g_RS, },
70 	{ "SH",	g_SH, },
71 	{ "SM",	g_SM, },
72 	{ "SS",	g_SS, },
73 	{ "TF",	g_ignore, },
74 	{ "TH",	g_TH, },
75 	{ "TP",	g_TP, },
76 
77 	{ "br",	g_br, },
78 	{ "ti",	g_br, },
79 	{ "nf",	g_nf, },
80 	{ "fi",	g_fi, },
81 	{ "ft",	g_ft, },
82 	{ nil, nil },
83 };
84 
85 
86 typedef struct Troffspec	Troffspec;
87 struct Troffspec
88 {
89 	char *name;
90 	char *value;
91 };
92 
93 static Troffspec tspec[] =
94 {
95 	{ "ff", "ff", },
96 	{ "fi", "fi", },
97 	{ "fl", "fl", },
98 	{ "Fi", "ffi", },
99 	{ "ru", "_", },
100 	{ "em", "&#173;", },
101 	{ "14", "&#188;", },
102 	{ "12", "&#189;", },
103 	{ "co", "&#169;", },
104 	{ "de", "&#176;", },
105 	{ "dg", "&#161;", },
106 	{ "fm", "&#180;", },
107 	{ "rg", "&#174;", },
108 	{ "bu", "*", },
109 	{ "sq", "&#164;", },
110 	{ "hy", "-", },
111 	{ "pl", "+", },
112 	{ "mi", "-", },
113 	{ "mu", "&#215;", },
114 	{ "di", "&#247;", },
115 	{ "eq", "=", },
116 	{ "==", "==", },
117 	{ ">=", ">=", },
118 	{ "<=", "<=", },
119 	{ "!=", "!=", },
120 	{ "+-", "&#177;", },
121 	{ "no", "&#172;", },
122 	{ "sl", "/", },
123 	{ "ap", "&", },
124 	{ "~=", "~=", },
125 	{ "pt", "oc", },
126 	{ "gr", "GRAD", },
127 	{ "->", "->", },
128 	{ "<-", "<-", },
129 	{ "ua", "^", },
130 	{ "da", "v", },
131 	{ "is", "Integral", },
132 	{ "pd", "DIV", },
133 	{ "if", "oo", },
134 	{ "sr", "-/", },
135 	{ "sb", "(~", },
136 	{ "sp", "~)", },
137 	{ "cu", "U", },
138 	{ "ca", "(^)", },
139 	{ "ib", "(=", },
140 	{ "ip", "=)", },
141 	{ "mo", "C", },
142 	{ "es", "&Oslash;", },
143 	{ "aa", "&#180;", },
144 	{ "ga", "`", },
145 	{ "ci", "O", },
146 	{ "L1", "DEATHSTAR", },
147 	{ "sc", "&#167;", },
148 	{ "dd", "++", },
149 	{ "lh", "<=", },
150 	{ "rh", "=>", },
151 	{ "lt", "(", },
152 	{ "rt", ")", },
153 	{ "lc", "|", },
154 	{ "rc", "|", },
155 	{ "lb", "(", },
156 	{ "rb", ")", },
157 	{ "lf", "|", },
158 	{ "rf", "|", },
159 	{ "lk", "|", },
160 	{ "rk", "|", },
161 	{ "bv", "|", },
162 	{ "ts", "s", },
163 	{ "br", "|", },
164 	{ "or", "|", },
165 	{ "ul", "_", },
166 	{ "rn", " ", },
167 	{ "**", "*", },
168 	{ nil, nil, },
169 };
170 
171 static	char *curfont;
172 static	char token[128];
173 static	int lastc = '\n';
174 
175 void	closel(void);
176 void	closeall(void);
177 void	doconvert(char*, int, int);
178 
179 /* get next logical character.  expand it with escapes */
180 char*
181 getnext(void)
182 {
183 	int r, mult, size;
184 	Rune rr;
185 	Htmlesc *e;
186 	Troffspec *t;
187 	char buf[32];
188 
189 	if(lastc == '\n')
190 		sol = 1;
191 	else
192 		sol = 0;
193 	r = Bgetrune(&in);
194 	if(r < 0)
195 		return nil;
196 	lastc = r;
197 	if(r > 128){
198 		for(e = htmlesc; e->name; e++)
199 			if(e->value == r)
200 				return e->name;
201 		rr = r;
202 		runetochar(buf, &rr);
203 		for(r = 0; r < runelen(rr); r++)
204 			snprint(token + 3*r, sizeof(token)-3*r, "%%%2.2ux", buf[r]);
205 		return token;
206 	}
207 	switch(r){
208 	case '\\':
209 		r = Bgetrune(&in);
210 		if(r < 0)
211 			return nil;
212 		lastc = r;
213 		switch(r){
214 		/* chars to ignore */
215 		case '|':
216 		case '&':
217 			return getnext();
218 
219 		/* ignore arg */
220 		case 'k':
221 			Bgetrune(&in);
222 			return getnext();
223 
224 		/* defined strings */
225 		case '*':
226 			switch(Bgetrune(&in)){
227 			case 'R':
228 				return "&#174;";
229 			}
230 			return getnext();
231 
232 		/* special chars */
233 		case '(':
234 			token[0] = Bgetc(&in);
235 			token[1] = Bgetc(&in);
236 			token[2] = 0;
237 			for(t = tspec; t->name; t++)
238 				if(strcmp(token, t->name) == 0)
239 					return t->value;
240 			return "&#191;";
241 		case 'c':
242 			r = Bgetrune(&in);
243 			if(r == '\n'){
244 				lastc = r;
245 				return getnext();
246 			}
247 			break;
248 		case 'e':
249 			return "\\";
250 			break;
251 		case 'f':
252 			lastc = r = Bgetrune(&in);
253 			switch(r){
254 			case '2':
255 			case 'B':
256 				strcpy(token, "<TT>");
257 				if(curfont)
258 					snprint(token, sizeof(token), "%s<TT>", curfont);
259 				curfont = "</TT>";
260 				return token;
261 			case '3':
262 			case 'I':
263 				strcpy(token, "<I>");
264 				if(curfont)
265 					snprint(token, sizeof(token), "%s<I>", curfont);
266 				curfont = "</I>";
267 				return token;
268 			case 'L':
269 				strcpy(token, "<TT>");
270 				if(curfont)
271 					snprint(token, sizeof(token), "%s<TT>", curfont);
272 				curfont = "</TT>";
273 				return token;
274 			default:
275 				token[0] = 0;
276 				if(curfont)
277 					strcpy(token, curfont);
278 				curfont = nil;
279 				return token;
280 			}
281 		case 's':
282 			mult = 1;
283 			size = 0;
284 			for(;;){
285 				r = Bgetc(&in);
286 				if(r < 0)
287 					return nil;
288 				if(r == '+')
289 					;
290 				else if(r == '-')
291 					mult *= -1;
292 				else if(r >= '0' && r <= '9')
293 					size = size*10 + (r-'0');
294 				else{
295 					Bungetc(&in);
296 					break;
297 				}
298 				lastc = r;
299 			}
300 			break;
301 		case ' ':
302 			return "&#32;";
303 		}
304 		break;
305 	case '<':
306 		return example ? "<" : "&#60;";
307 		break;
308 	case '>':
309 		return example ? ">" : "&#62;";
310 		break;
311 	}
312 	token[0] = r;
313 	token[1] = 0;
314 	return token;
315 }
316 
317 enum
318 {
319 	Narg = 32,
320 	Nline = 1024,
321 	Maxget = 10,
322 };
323 
324 void
325 dogoobie(void)
326 {
327 	char *p, *np, *e;
328 	Goobie *g;
329 	char line[Nline];
330 	int argc;
331 	char *argv[Narg];
332 
333 	/* read line, translate special chars */
334 	e = line + sizeof(line) - Maxget;
335 	for(p = line; p < e; ){
336 		np = getnext();
337 		if(np == nil)
338 			return;
339 		if(np[0] == '\n')
340 			break;
341 		if(np[1]) {
342 			strcpy(p, np);
343 			p += strlen(np);
344 		} else
345 			*p++ = np[0];
346 	}
347 	*p = 0;
348 
349 	/* parse into arguments */
350 	p = line;
351 	for(argc = 0; argc < Narg; argc++){
352 		while(*p == ' ' || *p == '\t')
353 			*p++ = 0;
354 		if(*p == 0)
355 			break;
356 		if(*p == '"'){
357 			*p++ = 0;
358 			argv[argc] = p;
359 			while(*p && *p != '"')
360 				p++;
361 			if(*p == '"')
362 				*p++ = 0;
363 		} else {
364 			argv[argc] = p;
365 			while(*p && *p != ' ' && *p != '\t')
366 				p++;
367 		}
368 	}
369 	argv[argc] = nil;
370 
371 	if(argc == 0)
372 		return;
373 
374 	for(g = gtab; g->name; g++)
375 		if(strncmp(g->name, argv[0], 2) == 0){
376 			(*g->f)(argc, argv);
377 			return;
378 		}
379 
380 	fprint(2, "unknown directive %s\n", line);
381 }
382 
383 void
384 printargs(int argc, char **argv)
385 {
386 	argc--;
387 	argv++;
388 	while(--argc > 0)
389 		hprint(hout, "%s ", *(argv++));
390 	if(argc == 0)
391 		hprint(hout, "%s", *argv);
392 }
393 
394 void
395 error(char *title, char *fmt, ...)
396 {
397 	va_list arg;
398 	char buf[1024], *out;
399 
400 	va_start(arg, fmt);
401 	out = doprint(buf, buf+sizeof(buf), fmt, arg);
402 	va_end(arg);
403 	*out = 0;
404 
405 	hprint(hout, "%s 404 %s\n", version, title);
406 	hprint(hout, "Date: %D\n", time(nil));
407 	hprint(hout, "Server: Plan9\n");
408 	hprint(hout, "Content-type: text/html\n");
409 	hprint(hout, "\n");
410 	hprint(hout, "<head><title>%s</title></head>\n", title);
411 	hprint(hout, "<body><h1>%s</h1></body>\n", title);
412 	hprint(hout, "%s\n", buf);
413 	exits(nil);
414 }
415 
416 typedef struct Hit	Hit;
417 struct Hit
418 {
419 	Hit *next;
420 	char *file;
421 };
422 
423 void
424 lookup(char *object, int section, Hit **list)
425 {
426 	int fd;
427 	char *p, *f;
428 	Biobuf b;
429 	char file[4*NAMELEN];
430 	Hit *h;
431 
432 	while(*list != nil)
433 		list = &(*list)->next;
434 
435 	snprint(file, sizeof(file), "/sys/man/%d/INDEX", section);
436 	fd = open(file, OREAD);
437 	if(fd > 0){
438 		Binit(&b, fd, OREAD);
439 		for(;;){
440 			p = Brdline(&b, '\n');
441 			if(p == nil)
442 				break;
443 			p[Blinelen(&b)-1] = 0;
444 			f = strchr(p, ' ');
445 			if(f == nil)
446 				continue;
447 			*f++ = 0;
448 			if(strcmp(p, object) == 0){
449 				h = ezalloc(sizeof *h);
450 				*list = h;
451 				h->next = nil;
452 				snprint(file, sizeof(file), "/%d/%s", section, f);
453 				h->file = estrdup(file);
454 				close(fd);
455 				return;
456 			}
457 		}
458 		close(fd);
459 	}
460 	snprint(file, sizeof(file), "/sys/man/%d/%s", section, object);
461 	if(access(file, 0) == 0){
462 		h = ezalloc(sizeof *h);
463 		*list = h;
464 		h->next = nil;
465 		h->file = estrdup(file+8);
466 	}
467 }
468 
469 void
470 manindex(int sect, int vermaj)
471 {
472 	int i;
473 
474 	if(vermaj){
475 		okheaders(connect);
476 		hprint(hout, "Content-type: text/html\r\n");
477 		hprint(hout, "\r\n");
478 	}
479 
480 	hprint(hout, "<head><title>plan 9 section index");
481 	if(sect)
482 		hprint(hout, "(%d)\n", sect);
483 	hprint(hout, "</title></head><body>\n");
484 	hprint(hout, "<H6>Section Index");
485 	if(sect)
486 		hprint(hout, "(%d)\n", sect);
487 	hprint(hout, "</H6>\n");
488 
489 	if(sect)
490 		hprint(hout, "<p><a href=\"/plan9/man%d.html\">/plan9/man%d.html</a>\n",
491 			sect, sect);
492 	else for(i = 1; i < 10; i++)
493 		hprint(hout, "<p><a href=\"/plan9/man%d.html\">/plan9/man%d.html</a>\n",
494 			i, i);
495 	hprint(hout, "</body>\n");
496 }
497 
498 void
499 man(char *o, int sect, int vermaj)
500 {
501 	int i;
502 	Hit *list;
503 
504 	list = nil;
505 
506 	if(*o == 0){
507 		manindex(sect, vermaj);
508 		return;
509 	}
510 
511 	if(sect > 0 && sect < 10)
512 		lookup(o, sect, &list);
513 	else
514 		for(i = 1; i < 9; i++)
515 			lookup(o, i, &list);
516 
517 	if(list != nil && list->next == nil){
518 		doconvert(list->file, vermaj, 0);
519 		return;
520 	}
521 
522 	if(vermaj){
523 		okheaders(connect);
524 		hprint(hout, "Content-type: text/html\r\n");
525 		hprint(hout, "\r\n");
526 	}
527 
528 	hprint(hout, "<head><title>plan 9 man %H", o);
529 	if(sect)
530 		hprint(hout, "(%d)\n", sect);
531 	hprint(hout, "</title></head><body>\n");
532 	hprint(hout, "<H6>Search for %H", o);
533 	if(sect)
534 		hprint(hout, "(%d)\n", sect);
535 	hprint(hout, "</H6>\n");
536 
537 	for(; list; list = list->next)
538 		hprint(hout, "<p><a href=\"/magic/man2html%U\">/magic/man2html%H</a>\n",
539 			list->file, list->file);
540 	hprint(hout, "</body>\n");
541 }
542 
543 void
544 redirectto(char *uri)
545 {
546 	if(connect)
547 		moved(connect, uri);
548 	else
549 		hprint(hout, "Your selection moved to <a href=\"%U\"> here</a>.<p></body>\r\n", uri);
550 }
551 
552 void
553 searchfor(char *search)
554 {
555 	int i, j, n, fd;
556 	char *p, *sp;
557 	Biobufhdr *b;
558 	char *arg[32];
559 
560 	hprint(hout, "<head><title>plan 9 search for %H</title></head>\n", search);
561 	hprint(hout, "<body>\n");
562 
563 	hprint(hout, "<p>This is a keyword search through Plan 9 man pages.\n");
564 	hprint(hout, "The search is case insensitive; blanks denote \"boolean and\".\n");
565 	hprint(hout, "<FORM METHOD=\"GET\" ACTION=\"/magic/man2html\">\n");
566 	hprint(hout, "<INPUT NAME=\"pat\" TYPE=\"text\" SIZE=\"60\">\n");
567 	hprint(hout, "<INPUT TYPE=\"submit\" VALUE=\"Submit\">\n");
568 	hprint(hout, "</FORM>\n");
569 
570 	hprint(hout, "<hr><H6>Search for %H</H6>\n", search);
571 	n = getfields(search, arg, 32, 1, "+");
572 	for(i = 0; i < n; i++){
573 		for(j = i+1; j < n; j++){
574 			if(strcmp(arg[i], arg[j]) > 0){
575 				sp = arg[j];
576 				arg[j] = arg[i];
577 				arg[i] = sp;
578 			}
579 		}
580 		sp = malloc(strlen(arg[i]) + 2);
581 		if(sp != nil){
582 			strcpy(sp+1, arg[i]);
583 			sp[0] = ' ';
584 			arg[i] = sp;
585 		}
586 	}
587 
588 	/*
589 	 *  search index till line starts alphabetically < first token
590 	 */
591 	fd = open("/sys/man/searchindex", OREAD);
592 	if(fd < 0){
593 		hprint(hout, "<body>error: No Plan 9 search index\n");
594 		hprint(hout, "</body>");
595 		return;
596 	}
597 	p = malloc(32*1024);
598 	if(p == nil){
599 		close(fd);
600 		return;
601 	}
602 	b = ezalloc(sizeof *b);
603 	Binits(b, fd, OREAD, (uchar*)p, 32*1024);
604 	for(;;){
605 		p = Brdline(b, '\n');
606 		if(p == nil)
607 			break;
608 		p[Blinelen(b)-1] = 0;
609 		for(i = 0; i < n; i++){
610 			sp = strstr(p, arg[i]);
611 			if(sp == nil)
612 				break;
613 			p = sp;
614 		}
615 		if(i < n)
616 			continue;
617 		sp = strrchr(p, '\t');
618 		if(sp == nil)
619 			continue;
620 		sp++;
621 		hprint(hout, "<p><a href=\"/magic/man2html/%U\">/magic/man2html/%H</a>\n",
622 			sp, sp);
623 	}
624 	hprint(hout, "</body>");
625 
626 	Bterm(b);
627 	free(b);
628 	free(p);
629 	close(fd);
630 }
631 
632 /*
633  *  find man pages mentioning the search string
634  */
635 void
636 dosearch(int vermaj, char *search)
637 {
638 	int sect;
639 	char *p;
640 
641 	if(strncmp(search, "man=", 4) == 0){
642 		sect = 0;
643 		search = urlunesc(search+4);
644 		p = strchr(search, '&');
645 		if(p != nil){
646 			*p++ = 0;
647 			if(strncmp(p, "sect=", 5) == 0)
648 				sect = atoi(p+5);
649 		}
650 		man(search, sect, vermaj);
651 		return;
652 	}
653 
654 	if(vermaj){
655 		okheaders(connect);
656 		hprint(hout, "Content-type: text/html\r\n");
657 		hprint(hout, "\r\n");
658 	}
659 
660 	if(strncmp(search, "pat=", 4) == 0){
661 		search = urlunesc(search+4);
662 		search = lower(search);
663 		searchfor(search);
664 		return;
665 	}
666 
667 	hprint(hout, "<head><title>illegal search</title></head>\n");
668 	hprint(hout, "<body><p>Illegally formatted Plan 9 man page search</p>\n");
669 	search = urlunesc(search);
670 	hprint(hout, "<body><p>%H</p>\n", search);
671 	hprint(hout, "</body>");
672 }
673 
674 void
675 dohangingdt(void)
676 {
677 	switch(hangingdt){
678 	case 3:
679 		hangingdt--;
680 		break;
681 	case 2:
682 		hprint(hout, "<dd>");
683 		hangingdt = 0;
684 		break;
685 	}
686 }
687 
688 /*
689  *  finish any one line font changes
690  */
691 void
692 dooneliner(void)
693 {
694 	if(onelinefont[0] != 0)
695 		hprint(hout, "%s", onelinefont);
696 	onelinefont[0] = 0;
697 }
698 
699 /*
700  *  convert a man page to html and output
701  */
702 void
703 doconvert(char *uri, int vermaj, int stdin)
704 {
705 	int fd;
706 	char c, *p;
707 	char file[256];
708 	Dir d;
709 
710 	if(stdin){
711 		fd = 0;
712 	} else {
713 		if(strstr(uri, ".."))
714 			error("bad URI", "man page URI cannot contain ..");
715 		p = strstr(uri, "/intro");
716 		if(p == nil){
717 			snprint(file, sizeof(file), "/sys/man/%s", uri);
718 			if(dirstat(file, &d) >= 0 && (d.qid.path & CHDIR)){
719 				if(*uri == 0 || strcmp(uri, "/") == 0)
720 					redirectto("/plan9/vol1.html");
721 				else {
722 					snprint(file, sizeof(file), "/plan9/man%s.html",
723 						uri+1);
724 					redirectto(file);
725 				}
726 				return;
727 			}
728 			if(*uri == '/')
729 				uri++;
730 			snprint(section, sizeof(section), uri);
731 			p = strstr(section, "/");
732 			if(p != nil)
733 				*p = 0;
734 		} else {
735 			*p = 0;
736 			snprint(file, sizeof(file), "/sys/man/%s/0intro", uri);
737 			if(*uri == '/')
738 				uri++;
739 			snprint(section, sizeof(section), uri);
740 		}
741 		fd = open(file, OREAD);
742 		if(fd < 0)
743 			error("non-existent man page", "cannot open %H: %r", file);
744 
745 		if(vermaj){
746 			okheaders(connect);
747 			hprint(hout, "Content-type: text/html\r\n");
748 			hprint(hout, "\r\n");
749 		}
750 
751 	}
752 
753 	Binit(&in, fd, OREAD);
754 	hprint(hout, "<html>\n<head>\n");
755 
756 	for(;;){
757 		p = getnext();
758 		if(p == nil)
759 			break;
760 		c = *p;
761 		if(c == '.' && sol){
762 			dogoobie();
763 			dohangingdt();
764 		} else if(c == '\n'){
765 			dooneliner();
766 			hprint(hout, "%s", p);
767 			dohangingdt();
768 		} else
769 			hprint(hout, "%s", p);
770 	}
771 	closeall();
772 	hprint(hout, "<br><font size=1><A href=http://www.lucent.com/copyright.html>\n");
773 	hprint(hout, "Copyright</A> &#169; 2000 Lucent Technologies.  All rights reserved.</font>\n");
774 	hprint(hout, "</body></html>\n");
775 }
776 
777 void
778 main(int argc, char **argv)
779 {
780 
781 	fmtinstall('H', httpconv);
782 	fmtinstall('U', urlconv);
783 
784 	if(argc == 2 && strcmp(argv[1], "-") == 0){
785 		hinit(&houtb, 1, Hwrite);
786 		hout = &houtb;
787 		doconvert(nil, 0, 1);
788 		exits(nil);
789 	}
790 	close(2);
791 
792 	connect = init(argc, argv);
793 	hout = &connect->hout;
794 	httpheaders(connect);
795 
796 	if(strcmp(connect->req.meth, "GET") != 0 && strcmp(connect->req.meth, "HEAD") != 0)
797 		unallowed(connect, "GET, HEAD");
798 	if(connect->head.expectother || connect->head.expectcont)
799 		fail(connect, ExpectFail, nil);
800 
801 	anonymous(connect);
802 
803 	if(connect->req.search != nil)
804 		dosearch(connect->req.vermaj, connect->req.search);
805 	else
806 		doconvert(connect->req.uri, connect->req.vermaj, 0);
807 	exits(nil);
808 }
809 
810 void
811 g_notyet(int, char **argv)
812 {
813 	fprint(2, ".%s not yet supported\n", argv[0]);
814 }
815 
816 void
817 g_ignore(int, char**)
818 {
819 }
820 
821 void
822 g_PP(int, char**)
823 {
824 	closel();
825 	hprint(hout, "<P>\n");
826 }
827 
828 /* close a list */
829 void
830 closel(void)
831 {
832 	switch(list){
833 	case Lordered:
834 		hprint(hout, "</ol>\n");
835 		break;
836 	case Lunordered:
837 		hprint(hout, "</ul>\n");
838 		break;
839 	case Lother:
840 	case Ldef:
841 		hprint(hout, "</dl>\n");
842 		break;
843 	}
844 	list = 0;
845 
846 }
847 
848 void
849 closeall(void)
850 {
851 	closel();
852 	while(indents > 0){
853 		indents--;
854 		hprint(hout, "</DL>\n");
855 	}
856 }
857 
858 void
859 g_IP(int argc, char **argv)
860 {
861 	switch(list){
862 	default:
863 		closel();
864 		if(argc > 1){
865 			if(strcmp(argv[1], "1") == 0){
866 				list = Lordered;
867 				listnum = 1;
868 				hprint(hout, "<OL>\n");
869 			} else if(strcmp(argv[1], "*") == 0){
870 				list = Lunordered;
871 				hprint(hout, "<UL>\n");
872 			} else {
873 				list = Lother;
874 				hprint(hout, "<DL>\n");
875 			}
876 		} else {
877 			list = Lother;
878 			hprint(hout, "<DL>\n");
879 		}
880 		break;
881 	case Lother:
882 	case Lordered:
883 	case Lunordered:
884 		break;
885 	}
886 
887 	switch(list){
888 	case Lother:
889 		hprint(hout, "<DT>");
890 		printargs(argc, argv);
891 		hprint(hout, "<DD>\n");
892 		break;
893 	case Lordered:
894 	case Lunordered:
895 		hprint(hout, "<LI>\n");
896 		break;
897 	}
898 }
899 
900 void
901 g_TP(int, char**)
902 {
903 	switch(list){
904 	default:
905 		closel();
906 		list = Ldef;
907 		hangingdt = 3;
908 		hprint(hout, "<DL><DT>\n");
909 		break;
910 	case Ldef:
911 		if(hangingdt)
912 			hprint(hout, "<DD>");
913 		hprint(hout, "<DT>");
914 		hangingdt = 3;
915 		break;
916 	}
917 }
918 
919 void
920 g_HP(int, char**)
921 {
922 	switch(list){
923 	default:
924 		closel();
925 		list = Ldef;
926 		hangingdt = 1;
927 		hprint(hout, "<DL><DT>\n");
928 		break;
929 	case Ldef:
930 		if(hangingdt)
931 			hprint(hout, "<DD>");
932 		hprint(hout, "<DT>");
933 		hangingdt = 1;
934 		break;
935 	}
936 }
937 
938 void
939 g_LP(int argc, char **argv)
940 {
941 	g_PP(argc, argv);
942 }
943 
944 void
945 g_TH(int argc, char **argv)
946 {
947 	if(argc > 1){
948 		hprint(hout, "<title>");
949 		if(argc > 2)
950 			hprint(hout, "Plan 9's %s(%s)\n", argv[1], argv[2]);
951 		else
952 			hprint(hout, "Plan 9's %s()\n", argv[1]);
953 		hprint(hout, "</title>\n");
954 		hprint(hout, "</head><body>\n");
955 		hprint(hout, "<B>[<A href=/sys/man/index.html>manual index</A>]</B>");
956 		hprint(hout, "<B>[<A href=/sys/man/%s/INDEX.html>section index</A>]</B>\n", section);
957 	} else
958 		hprint(hout, "</head><body>\n");
959 }
960 
961 void
962 g_SH(int argc, char **argv)
963 {
964 	closeall();
965 	indents++;
966 	hprint(hout, "<H4>");
967 	printargs(argc, argv);
968 	hprint(hout, "</H4>\n");
969 	hprint(hout, "<DL><DT><DD>\n");
970 }
971 
972 void
973 g_SS(int argc, char **argv)
974 {
975 	closeall();
976 	indents++;
977 	hprint(hout, "<H5>");
978 	printargs(argc, argv);
979 	hprint(hout, "</H5>\n");
980 	hprint(hout, "<DL><DT><DD>\n");
981 }
982 
983 void
984 font(char *f, int argc, char **argv)
985 {
986 	if(argc < 2){
987 		hprint(hout, "<%s>", f);
988 		sprint(onelinefont, "</%s>", f);
989 	} else {
990 		hprint(hout, "<%s>", f);
991 		printargs(argc, argv);
992 		hprint(hout, "</%s>\n", f);
993 	}
994 }
995 
996 void
997 altfont(char *f1, char *f2, int argc, char **argv)
998 {
999 	char *f;
1000 	int i;
1001 
1002 	argc--;
1003 	argv++;
1004 	for(i = 0; i < argc; i++){
1005 		if(i & 1)
1006 			f = f2;
1007 		else
1008 			f = f1;
1009 		if(strcmp(f, "R") == 0)
1010 			hprint(hout, "%s", argv[i]);
1011 		else
1012 			hprint(hout, "<%s>%s</%s>", f, argv[i], f);
1013 	}
1014 	hprint(hout, "\n");
1015 }
1016 
1017 void
1018 g_B(int argc, char **argv)
1019 {
1020 	font("TT", argc, argv);
1021 }
1022 
1023 void
1024 g_BI(int argc, char **argv)
1025 {
1026 	altfont("TT", "I", argc, argv);
1027 }
1028 
1029 void
1030 g_BR(int argc, char **argv)
1031 {
1032 	altfont("TT", "R", argc, argv);
1033 }
1034 
1035 void
1036 g_RB(int argc, char **argv)
1037 {
1038 	altfont("R", "TT", argc, argv);
1039 }
1040 
1041 void
1042 g_I(int argc, char **argv)
1043 {
1044 	font("I", argc, argv);
1045 }
1046 
1047 void
1048 g_IB(int argc, char **argv)
1049 {
1050 	altfont("I", "TT", argc, argv);
1051 }
1052 
1053 void
1054 g_IR(int argc, char **argv)
1055 {
1056 	int anchor;
1057 	char *p;
1058 
1059 	anchor = 0;
1060 	if(argc > 2){
1061 		p = argv[2];
1062 		if(p[0] == '(' && p[1] >= '1' && p[1] <= '9' && p[2] == ')')
1063 			anchor = 1;
1064 		if(anchor)
1065 			hprint(hout, "<A href=\"/magic/man2html/%c/%U\">",
1066 				p[1], httpunesc(lower(argv[1])));
1067 	}
1068 	altfont("I", "R", argc, argv);
1069 	if(anchor)
1070 		hprint(hout, "</A>");
1071 }
1072 
1073 void
1074 g_RI(int argc, char **argv)
1075 {
1076 	altfont("R", "I", argc, argv);
1077 }
1078 
1079 void
1080 g_br(int, char**)
1081 {
1082 	if(hangingdt){
1083 		hprint(hout, "<dd>");
1084 		hangingdt = 0;
1085 	}else
1086 		hprint(hout, "<br>\n");
1087 }
1088 
1089 void
1090 g_nf(int, char**)
1091 {
1092 	example = 1;
1093 	hprint(hout, "<PRE>\n");
1094 }
1095 
1096 void
1097 g_fi(int, char**)
1098 {
1099 	example = 0;
1100 	hprint(hout, "</PRE>\n");
1101 }
1102 
1103 void
1104 g_EX(int, char**)
1105 {
1106 	example = 1;
1107 	hprint(hout, "<TT><XMP>\n");
1108 }
1109 
1110 void
1111 g_EE(int, char**)
1112 {
1113 	example = 0;
1114 	hprint(hout, "</XMP></TT>\n");
1115 }
1116 
1117 void
1118 g_L(int argc, char **argv)
1119 {
1120 	font("TT", argc, argv);
1121 }
1122 
1123 void
1124 g_LR(int argc, char **argv)
1125 {
1126 	altfont("TT", "R", argc, argv);
1127 }
1128 
1129 void
1130 g_RL(int argc, char **argv)
1131 {
1132 	altfont("R", "TT", argc, argv);
1133 }
1134 
1135 void
1136 g_RS(int, char**)
1137 {
1138 	hprint(hout, "<DL><DT><DD>\n");
1139 }
1140 
1141 void
1142 g_RE(int, char**)
1143 {
1144 	hprint(hout, "</DL>\n");
1145 }
1146 
1147 void
1148 g_SM(int argc, char **argv)
1149 {
1150 	if(argc > 1)
1151 		hprint(hout, "%s ", argv[1]);
1152 }
1153 
1154 void
1155 g_ft(int argc, char **argv)
1156 {
1157 	char *font;
1158 
1159 	if(argc < 2)
1160 		return;
1161 
1162 	if(curfont)
1163 		hprint(hout, "%s", curfont);
1164 
1165 	switch(*argv[1]){
1166 	case '2':
1167 	case 'B':
1168 		font = "B";
1169 		curfont = "</B>";
1170 		break;
1171 	case '3':
1172 	case 'I':
1173 		font = "I";
1174 		curfont = "</I>";
1175 		break;
1176 	case 'L':
1177 		font = "TT";
1178 		curfont = "</TT>";
1179 		break;
1180 	default:
1181 		curfont = nil;
1182 		return;
1183 	}
1184 	hprint(hout, "<%s>", font);
1185 }
1186