markman

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

markman.c (8243B)


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