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