markman

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

markman.c (8511B)


      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 		disp_line(b->v.l);
    346 		puts("\n.PP");
    347 		break;
    348 	case BCODE:
    349 		printf(".RS 4\n.EX\n%s\n.EE\n.RE\n", b->v.s);
    350 		break;
    351 	case ULIST:
    352 		printf(".IP ∙ 2\n");
    353 		disp_line(b->v.l);
    354 		puts("\n.PP");
    355 	case QUOTE:
    356 	case OLIST:
    357 		break;
    358 	}
    359 	if (b->next)
    360 		disp_block(b->next, b, new_lvl);
    361 }
    362 
    363 void
    364 markman_disp(Block b, char *name)
    365 {
    366 	if (!b) return;
    367 	if (b->t != HEADER || b->v.h.lvl != 1 || namesec) {
    368 		printf(".TH %s %ld ", str_cap(name), sec);
    369 		if (date) {
    370 			printf("\"%s\" ", date);
    371 		} else {
    372 			/* TODO option to configure date format */
    373 			time_t tt = time(NULL);
    374 			struct tm *t = localtime(&tt);
    375 			printf("%d-%02d-%02d ", t->tm_year+1900, t->tm_mon+1, t->tm_mday);
    376 		}
    377 		if (ver)
    378 			printf("\"%s\" ", ver);
    379 		if (mid)
    380 			printf("\"%s\"", mid);
    381 		putchar('\n');
    382 	}
    383 	if (namesec && b->t == HEADER) {
    384 		puts(".PP\n.SH NAME");
    385 		disp_line(b->v.h.l);
    386 		putchar('\n');
    387 	}
    388 	if (synsec)
    389 		printf(".PP\n.SH SYNOPSIS\n%s\n", synsec);
    390 	if (descsec)
    391 		puts(".PP\n.SH DESCRIPTION");
    392 	disp_block(b, NULL, 1);
    393 }
    394 
    395 static char*
    396 str_file(int fd)
    397 {
    398 	char buf[BUFSIZ], *file = NULL;
    399 	int len = 0, n;
    400 	while ((n = read(fd, buf, sizeof(buf))) > 0) {
    401 		file = realloc(file, len + n + 1);
    402 		if (!file) die(1, "realloc:");
    403 		memcpy(file + len, buf, n);
    404 		len += n;
    405 		file[len] = '\0';
    406 	}
    407 	if (fd)
    408 		close(fd);
    409 	if (n < 0)
    410 		die(1, "read:");
    411 	return file;
    412 }
    413 
    414 static void
    415 usage(const int eval)
    416 {
    417 	die(eval, "usage: %s [-cChv] [-t TITLE] [-d DATE] [-SECNUM] [FILENAME]", argv0);
    418 }
    419 
    420 int
    421 main(int argc, char *argv[])
    422 {
    423 
    424 	ARGBEGIN {
    425 	case 'c':
    426 		cap = 0;
    427 		break;
    428 	case 'C':
    429 		cap = 1;
    430 		break;
    431 	ARGNUM:
    432 		if ((sec = ARGNUMF()) > 8 || sec == 0)
    433 			die(1, "%s: invalid section number, expect 1-8, received [%d]", argv0, sec);
    434 		break;
    435 	case 't':
    436 		title = EARGF(usage(1));
    437 		break;
    438 	case 'd':
    439 		date = EARGF(usage(1));
    440 		break;
    441 	case 'V':
    442 		ver = EARGF(usage(1));
    443 		break;
    444 	case 'm':
    445 		mid = EARGF(usage(1));
    446 		break;
    447 	case 'n':
    448 		namesec = 1;
    449 		break;
    450 	case 's':
    451 		synsec = EARGF(usage(1));
    452 		break;
    453 	case 'D':
    454 		descsec = 1;
    455 		break;
    456 	case 'h':
    457 		usage(0);
    458 	case 'v':
    459 		die(0, "%s v%s (c) 2017-2022 Ed van Bruggen", argv0, VERSION);
    460 	default:
    461 		usage(1);
    462 	} ARGEND;
    463 
    464 	char *file;
    465 	int fd;
    466 	if (*argv) {
    467 		if ((fd = open(*argv, O_RDONLY)) < 0)
    468 			die(1, "%s: %s:", argv0, *argv);
    469 		file = str_file(fd);
    470 		if (!title) title = *argv;
    471 		markman_disp(markman_parse(file), title);
    472 	} else {
    473 		file = str_file(0);
    474 		markman_disp(markman_parse(file), "stdin");
    475 	}
    476 	free(file);
    477 	putchar('\n');
    478 
    479 	return EXIT_SUCCESS;
    480 }