markman

markdown man page converter
git clone git://edryd.org/markman
Log | Files | Refs | README | LICENSE

markman.c (8687B)


      1 /* See LICENSE file for copyright and license details. */
      2 #include <ctype.h>
      3 #include <fcntl.h>
      4 #include <libgen.h>
      5 #include <stdarg.h>
      6 #include <stdio.h>
      7 #include <stdlib.h>
      8 #include <string.h>
      9 #include <time.h>
     10 #include <unistd.h>
     11 
     12 #include "arg.h"
     13 
     14 #include "config.h"
     15 
     16 #define WS " \t\n"
     17 
     18 char *argv0;
     19 
     20 struct Block;
     21 typedef struct Block *Block;
     22 struct Line;
     23 typedef struct Line *Line;
     24 
     25 typedef enum {
     26 	STR,
     27 	BOLD,
     28 	ITALIC,
     29 	LINK,
     30 	LCODE,
     31 } LineType;
     32 
     33 typedef enum {
     34 	BR,
     35 	HEADER,
     36 	PARA,
     37 	QUOTE,
     38 	ULIST,
     39 	OLIST,
     40 	BCODE,
     41 } BlockType;
     42 
     43 struct Line {
     44 	LineType t;
     45 	union {
     46 		char *s; /* STR, BOLD, ITALIC, LCODE */
     47 		struct { /* LINK */
     48 			Line  txt;
     49 			char *url;
     50 		} u;
     51 	} v;
     52 	Line next;
     53 };
     54 
     55 struct Block {
     56 	BlockType t; /* BR */
     57 	union {
     58 		char *s; /* BCODE */
     59 		Line  l; /* PARA, QUOTE, */
     60 		struct { /* HEADER */
     61 			int lvl;
     62 			Line l;
     63 		} h;
     64 	} v;
     65 	Block next;
     66 };
     67 
     68 
     69 void
     70 die(int eval, const char *fmt, ...)
     71 {
     72 	va_list ap;
     73 
     74 	va_start(ap, fmt);
     75 	vfprintf(stderr, fmt, ap);
     76 	va_end(ap);
     77 
     78 	if (fmt[0] && fmt[strlen(fmt)-1] == ':')
     79 		fputc(' ', stderr),
     80 		perror(NULL);
     81 	else
     82 		fputc('\n', stderr);
     83 
     84 	exit(eval);
     85 }
     86 
     87 static char*
     88 str_cap(char *s)
     89 {
     90 	char *p;
     91 	if (cap)
     92 		for (p = s; *p; p++)
     93 			*p = toupper(*p);
     94 	return s;
     95 }
     96 
     97 static Line
     98 mk_str(char *s, LineType t)
     99 {
    100 	Line l = malloc(sizeof(struct Line));
    101 	if (!l) die(1, "malloc:");
    102 	l->t = t;
    103 	if (!(l->v.s = strdup(s)))
    104 		die(1, "strdup:");
    105 	l->next = NULL;
    106 	return l;
    107 }
    108 
    109 static Block
    110 mk_header(int lvl, Line l)
    111 {
    112 	Block b = malloc(sizeof(struct Block));
    113 	if (!b) die(1, "malloc:");
    114 	b->t = HEADER;
    115 	b->v.h.lvl = lvl;
    116 	b->v.h.l   = l;
    117 	b->next = NULL;
    118 	return b;
    119 }
    120 
    121 static Block
    122 mk_para(Line l)
    123 {
    124 	Block b = malloc(sizeof(struct Block));
    125 	if (!b) die(1, "malloc:");
    126 	b->t = PARA;
    127 	b->v.l  = l;
    128 	b->next = NULL;
    129 	return b;
    130 }
    131 
    132 static Block
    133 mk_ulist(Line l)
    134 {
    135 	Block b = malloc(sizeof(struct Block));
    136 	if (!b) die(1, "malloc:");
    137 	b->t = ULIST;
    138 	b->v.l  = l;
    139 	b->next = NULL;
    140 	return b;
    141 }
    142 
    143 static Block
    144 mk_bcode(char *s)
    145 {
    146 	Block b = malloc(sizeof(struct Block));
    147 	if (!b) die(1, "malloc:");
    148 	b->t = BCODE;
    149 	if (!(b->v.s = strdup(s)))
    150 		die(1, "strdup:");
    151 	b->next = NULL;
    152 	return b;
    153 }
    154 
    155 static Line
    156 line_parse(char *src, LineType t)
    157 {
    158 	Line ret;
    159 	char *s;
    160 	for (s = src; *src; src++) /* TODO rewrite this mess */
    161 		if (((*src == '*' && src[1] == '*') ||
    162 		     (*src == '_' && src[1] == '_')) && t != LCODE) {
    163 			*src = '\0';
    164 			ret = mk_str(s, t);
    165 			src += 2;
    166 			ret->next = line_parse(src, t == BOLD ? STR : BOLD);
    167 			return ret;
    168 		} else if ((*src == '*' || *src == '_') && t != LCODE) {
    169 			*src = '\0';
    170 			ret = mk_str(s, t);
    171 			src += 1;
    172 			ret->next = line_parse(src, t == ITALIC ? STR : ITALIC);
    173 			return ret;
    174 		} else if (*src == '`') {
    175 			*src = '\0';
    176 			ret = mk_str(s, t);
    177 			src += 1;
    178 			ret->next = line_parse(src, t == LCODE ? STR : LCODE);
    179 			return ret;
    180 		} else if (*src == '[' && t != LCODE) {
    181 			*src = '\0';
    182 			ret = mk_str(s, t);
    183 			src += 1;
    184 			ret->next = line_parse(src, LINK);
    185 			return ret;
    186 		} else if (*src == ']' && t == LINK && t != LCODE) {
    187 			*src = '\0';
    188 			ret = mk_str(s, t);
    189 			src += 1;
    190 			src += strcspn(src, WS);
    191 			ret->next = line_parse(src, STR);
    192 			return ret;
    193 		} else if (*src == '!' && src[1] == '[' && t != LCODE) {
    194 			*src = '\0';
    195 			ret = mk_str(s, t);
    196 			src += 1;
    197 			src += strcspn(src, "]") + 1;
    198 			src += strcspn(src, "])") + 1;
    199 			ret->next = line_parse(++src, STR);
    200 			return ret;
    201 		}
    202 	return mk_str(s, STR);
    203 }
    204 
    205 Block
    206 markman_parse(char *src)
    207 {
    208 	Block ret;
    209 	int lvl = 1, i;
    210 	char *s;
    211 	switch (*src) {
    212 	case '#':
    213 		for (; *src && *(++src) == '#'; lvl++);
    214 		src += strspn(src, WS);
    215 		s = src;
    216 		src += strcspn(src, "\n");
    217 		*src = '\0';
    218 		ret = mk_header(lvl, line_parse(lvl > 2 || (lvl == 1 && namesec)
    219 		                                ? s : str_cap(s), STR));
    220 		if (lvl > 3)
    221 			ret->v.h.l->t = BOLD;
    222 		ret->next = markman_parse(++src);
    223 		return ret;
    224 	case '\n':
    225 		return markman_parse(++src);
    226 	case '\0':
    227 		return NULL;
    228 	case '`':
    229 		if (src[1] == '`' && src[2] == '`')
    230 			src += strcspn(src, "\n");
    231 		for (s = src; *src; src++)
    232 			if (!strncmp(src, "\n```\n", 5)) {
    233 				src[1] = '\0';
    234 				src += 5;
    235 				break;
    236 			}
    237 		ret = mk_bcode(s);
    238 		ret->next = markman_parse(src);
    239 		return ret;
    240 	case '*': /* TODO support -  */
    241 		if (src[1] == ' ') {
    242 			/* TODO fix multiline points */
    243 			for (s = src; *src; src++)
    244 				/* TODO support spaces before list */
    245 				if (!strncmp(src, "\n* ", 3)) {
    246 					src[0] = '\0';
    247 					src += 1;
    248 					break;
    249 				} else if (!strncmp(src, "\n\n", 2)) {
    250 					src[0] = '\0';
    251 					src += 2;
    252 					break;
    253 				}
    254 			ret = mk_ulist(line_parse(s+2, STR));
    255 			ret->next = markman_parse(src);
    256 			return ret;
    257 		}
    258 	default:
    259 		src += strspn(src, WS);
    260 		s = calloc(strlen(src), sizeof(char));
    261 		if (!s) die(1, "calloc:");
    262 		for (i = 0; *src; src++, i++)
    263 			if (*src == '\n' && src[1] == '\n') {
    264 				s[i+1] = '\0';
    265 				break;
    266 			} else if (*src == '\n') {
    267 				s[i] = ' ';
    268 			} else {
    269 				s[i] = *src;
    270 			}
    271 		src += strspn(src, WS);
    272 		ret = mk_para(line_parse(s, STR));
    273 		ret->next = markman_parse(src);
    274 		return ret;
    275 	}
    276 }
    277 
    278 static void
    279 print_esc(const char *str)
    280 {
    281 	for (; *str; str++) {
    282 		if (*str == '\\')
    283 			putchar('\\');
    284 		putchar(*str);
    285 	}
    286 }
    287 
    288 void
    289 disp_line(Line l)
    290 {
    291 	switch (l->t) {
    292 	case LINK:
    293 	case STR:
    294 		printf(l->v.s);
    295 		break;
    296 	case BOLD:
    297 		printf("\\fB%s\\fP", l->v.s);
    298 		break;
    299 	case ITALIC:
    300 		printf("\\fI%s\\fP", l->v.s);
    301 		break;
    302 	case LCODE:
    303 		putchar('\'');
    304 		print_esc(l->v.s);
    305 		putchar('\'');
    306 		break;
    307 	}
    308 	if (l->next)
    309 		disp_line(l->next);
    310 }
    311 
    312 void
    313 disp_block(Block b, Block prev, int lvl)
    314 {
    315 	int new_lvl = lvl;
    316 	switch (b->t) {
    317 	case BR:
    318 		break;
    319 	case HEADER:
    320 		if (b->v.h.lvl == 1 && namesec) break;
    321 		switch (b->v.h.lvl) {
    322 		case 1:
    323 			printf(".TH ");
    324 			break;
    325 		case 2:
    326 			printf(".SH ");
    327 			break;
    328 		case 3:
    329 			printf(".SS ");
    330 			break;
    331 		case 4:
    332 			printf(".TP\n");
    333 			break;
    334 		}
    335 		new_lvl = b->v.h.lvl;
    336 		disp_line(b->v.h.l);
    337 		putchar('\n');
    338 		break;
    339 	case PARA:
    340 		if (prev && prev->t == HEADER) {
    341 			if (prev->v.h.lvl < 4)
    342 				puts(".PP");
    343 		} else if (lvl == 4)
    344 			puts(".IP");
    345 		else
    346 			puts(".PP");
    347 		disp_line(b->v.l);
    348 		puts("\n.PP");
    349 		break;
    350 	case BCODE:
    351 		printf(".RS 4\n.EX\n%s\n.EE\n.RE\n", b->v.s);
    352 		break;
    353 	case ULIST:
    354 		printf(".IP %s\\(bu", lvl >= 4 ? "\t" : " ");
    355 		if (prev && prev->t != ULIST)
    356 			puts(lvl < 4 ? " 2" : " 8");
    357 		else
    358 			puts("");
    359 		disp_line(b->v.l);
    360 		puts("");
    361 	case QUOTE:
    362 	case OLIST:
    363 		break;
    364 	}
    365 	if (b->next)
    366 		disp_block(b->next, b, new_lvl);
    367 }
    368 
    369 void
    370 markman_disp(Block b, char *name)
    371 {
    372 	if (!b) return;
    373 	if (b->t != HEADER || b->v.h.lvl != 1 || namesec) {
    374 		printf(".TH %s %ld", str_cap(name), sec);
    375 		if (date) {
    376 			printf(" \"%s\"", date);
    377 		} else {
    378 			/* TODO option to configure date format */
    379 			time_t tt = time(NULL);
    380 			struct tm *t = localtime(&tt);
    381 			printf(" %d-%02d-%02d", t->tm_year+1900, t->tm_mon+1, t->tm_mday);
    382 		}
    383 		if (ver)
    384 			printf(" \"%s\"", ver);
    385 		if (mid)
    386 			printf(" \"%s\"", mid);
    387 		putchar('\n');
    388 	}
    389 	if (namesec && b->t == HEADER) {
    390 		puts(".PP\n.SH NAME");
    391 		disp_line(b->v.h.l);
    392 		putchar('\n');
    393 	}
    394 	if (synsec)
    395 		printf(".PP\n.SH SYNOPSIS\n%s\n", synsec);
    396 	if (descsec)
    397 		puts(".PP\n.SH DESCRIPTION");
    398 	disp_block(b, NULL, 1);
    399 }
    400 
    401 static char*
    402 str_file(int fd)
    403 {
    404 	char buf[BUFSIZ], *file = NULL;
    405 	int len = 0, n;
    406 	while ((n = read(fd, buf, sizeof(buf))) > 0) {
    407 		file = realloc(file, len + n + 1);
    408 		if (!file) die(1, "realloc:");
    409 		memcpy(file + len, buf, n);
    410 		len += n;
    411 		file[len] = '\0';
    412 	}
    413 	if (fd)
    414 		close(fd);
    415 	if (n < 0)
    416 		die(1, "read:");
    417 	return file;
    418 }
    419 
    420 static void
    421 usage(const int eval)
    422 {
    423 	die(eval, "usage: %s [-cCnDhv] [-t TITLE] [-d DATE] [-V VERSION] [-m MIDDLE] [-s SYNOPSIS] [-SECNUM] [FILE]",
    424 	          argv0);
    425 }
    426 
    427 int
    428 main(int argc, char *argv[])
    429 {
    430 
    431 	ARGBEGIN {
    432 	case 'c':
    433 		cap = 0;
    434 		break;
    435 	case 'C':
    436 		cap = 1;
    437 		break;
    438 	ARGNUM:
    439 		if ((sec = ARGNUMF()) > 8 || sec == 0)
    440 			die(1, "%s: invalid section number, expect 1-8, received [%d]", argv0, sec);
    441 		break;
    442 	case 't':
    443 		title = EARGF(usage(1));
    444 		break;
    445 	case 'd':
    446 		date = EARGF(usage(1));
    447 		break;
    448 	case 'V':
    449 		ver = EARGF(usage(1));
    450 		break;
    451 	case 'm':
    452 		mid = EARGF(usage(1));
    453 		break;
    454 	case 'n':
    455 		namesec = 1;
    456 		break;
    457 	case 's':
    458 		synsec = EARGF(usage(1));
    459 		break;
    460 	case 'D':
    461 		descsec = 1;
    462 		break;
    463 	case 'h':
    464 		usage(0);
    465 	case 'v':
    466 		die(0, "%s v%s (c) 2017-2025 Edryd van Bruggen", argv0, VERSION);
    467 	default:
    468 		usage(1);
    469 	} ARGEND;
    470 
    471 	char *file;
    472 	int fd;
    473 	if (*argv) {
    474 		if ((fd = open(*argv, O_RDONLY)) < 0)
    475 			die(1, "%s: %s:", argv0, *argv);
    476 		file = str_file(fd);
    477 		if (!title) title = *argv;
    478 		markman_disp(markman_parse(file), title);
    479 	} else {
    480 		file = str_file(0);
    481 		markman_disp(markman_parse(file), "stdin");
    482 	}
    483 	free(file);
    484 	putchar('\n');
    485 
    486 	return EXIT_SUCCESS;
    487 }