/* * nihdoc.c -- Not Invented Here Doc * - - - --- * * ~akarle, MIT License * * "because markdown isn't in base!" */ #include #ifndef __PLAN_9 #include #endif #include /* Global Constants and Enums */ char *FMT_STRS[] = { ['_'] = "em", ['*'] = "strong", ['`'] = "code", }; enum Block { NONE, HEADER, HEADER_PARSE, PARAGRAPH, CODE, LIST, LIST_PARSE, }; enum Link { NOL, URL_PARSE, DESC_PARSE, OPT_URL, }; /* Start Global State */ enum Block in = NONE; enum Link in_link = NOL; int hlvl = 0; bool fmts[256] = {false}; /* indexed by _ * ` */ bool escape = false; char lnkdes[2048] = {0}; int lnkidx = 0; int indent = 0; int previndent = 0; int listdepth = 0; int lastc = '0'; bool ol = false; bool linestarted = false; bool blockquote = false; /* Helper functions */ void putesc(int c) { switch (c) { case '<': printf("<"); break; case '>': printf(">"); break; case '&': printf("&"); break; default: putchar(c); } } void newlist(void) { in = LIST; previndent = indent; printf("<%s>\n
  • \n", ol ? "ol" : "ul"); listdepth++; } int endlist(void) { in = LIST; previndent = indent; printf("
  • \n\n", ol ? "ol" : "ul"); return --listdepth; } /* * All inline types should start the paragraph if no other major type present. * This function does just that, IFF we aren't already in a major type. */ void maybe_startp(void) { if (in == NONE) { in = PARAGRAPH; printf("

    \n"); } } void handle_lf(void) { indent = 0; linestarted = false; /* single line types (one lf to close) */ if (in == HEADER) { in = NONE; printf("\n", hlvl); } /* multi-line types (two lf to close) */ if (lastc == '\n' || (lastc == '>' && blockquote)) { switch (in) { case PARAGRAPH: printf("

    \n"); break; case CODE: printf("\n"); break; case LIST: previndent = 0; while (endlist()) ; break; default: break; /* no op */ } in = NONE; if (blockquote && lastc == '\n') { printf("\n"); blockquote = false; } } } /* Returns whether inline styles should be allowed at this moment. */ bool fmt_disabled(int c) { /* `` blocks all but the next `, likewise CODE makes all disabled */ if (in == CODE || in_link == URL_PARSE) return true; else return fmts['`'] && c != '`'; } void toggle_format(int c) { if (!fmt_disabled(c)) { maybe_startp(); printf("<%s%s>", fmts[c] ? "/" : "", FMT_STRS[c]); fmts[c] ^= true; } else putesc(c); } /* * The main state machine [1], abstracted into a function to allow * playback support for buffered types (i.e. link descriptions) * * [1]: home grown spaghetti code */ void handlec(int c) { /* * Store link descriptions as we go, skipping the regular loop since * we'll play them back later via recursion on handlec */ if (in_link == DESC_PARSE && c != ']') { lnkdes[lnkidx++] = c; return; } /* Any character other than a '(' terminates a link at ']' */ if (in_link == OPT_URL && c != '(') { in_link = NOL; lnkdes[lnkidx] = '\0'; printf("%s", lnkdes, lnkdes); } /* Handle Escapes before any other bit of the main switch */ if (escape) { maybe_startp(); putesc(c); escape = false; return; } /* Store the indentation and return without printing */ if (!linestarted && c == ' ') { indent++; return; } switch (c) { case '\\': escape = true; break; case '#': if (in == NONE) { in = HEADER_PARSE; hlvl = 1; } else if (in == HEADER_PARSE) hlvl++; else putesc(c); break; case ' ': if (in == HEADER_PARSE) { printf("", hlvl); in = HEADER; } else if (in == LIST_PARSE) { if (!listdepth) { newlist(); } else { if (previndent < indent) { newlist(); } else if (previndent > indent) { endlist(); printf("\n
  • \n"); } else { in = LIST; printf("
  • \n
  • \n"); } } } else putesc(c); break; case '*': case '`': case '_': toggle_format(c); break; case '\t': if (in == NONE) { in = CODE; printf("
    ");
    		} else if (lastc != '\n')
    			putesc(c);
    		break;
    	case '>':
    		if (in == NONE) {
    			in = PARAGRAPH;
    			printf("%s", blockquote ? "

    \n" : "

    \n

    \n"); blockquote = true; } else if (lastc != '\n') putesc(c); break; case '[': if (in_link == NOL && !fmt_disabled(c)) { maybe_startp(); in_link = DESC_PARSE; lnkidx = 0; } else putesc(c); break; case ']': if (in_link == DESC_PARSE) in_link = OPT_URL; else putesc(c); break; case '(': if (in_link == OPT_URL) { in_link = URL_PARSE; printf(""); for (int i = 0; i < lnkidx; i++) handlec(lnkdes[i]); printf(""); } else putesc(c); break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '.': case '-': if (in == NONE || !linestarted) { ol = c != '-'; in = LIST_PARSE; } else if (in != LIST_PARSE) putesc(c); break; case '\n': handle_lf(); if (in != NONE) putesc(c); break; default: maybe_startp(); putesc(c); break; } lastc = c; linestarted = c != '\n'; } /* nihdoc: a text -> HTML parser */ int main(int argc, char *argv[]) { int c; #ifdef __OpenBSD__ pledge("stdio", "stdio"); #endif if (argc > 1) { fprintf(stderr, "error: %s takes no arguments\n", argv[0]); return 1; } while ((c = getchar()) != EOF) handlec(c); /* pretend there's a final LF to close any blocks */ handle_lf(); return 0; }