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 }