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 }