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 }