markman

Unnamed repository; edit this file 'description' to name the repository.
git clone git://edryd.org/markman
Log | Files | Refs | LICENSE

markman.c (7752B)


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