/* aya.c - Generates a documentation page for a Parsee function * ============================================================ * Not hdoc because I hate hdoc!!!!!!111!!!!!!1!!!!11111!!! * TODO: Aya is currently incomplete. Sorry, not sorry. * * Under CC0, as its a rather useful example of a KappaChat tool. * See LICENSE for more information about Parsee's licensing. */ #include #include #include #include #include #include #include #include #include #include typedef struct AyadocComment { /* TODO: Descriptions are a bit more advanced than a raw string. * Only because you have to deal with the {value} format. */ char *description; /* Some notes are more advanced (see Returns, Modifies, See-Also, ...) */ HashMap *notes; } AyadocComment; static void FreeAyadoc(AyadocComment *comment) { char *key, *value; if (!comment) { return; } Free(comment->description); while (HashMapIterate(comment->notes, &key, (void **) &value)) { Free(value); } HashMapFree(comment->notes); Free(comment); } static AyadocComment * ParseAyadoc(char *raw) { AyadocComment *ret; char *start, *line_break; bool end_of_text = false; bool parsing_notes = false; if (!raw) { return NULL; } ret = Malloc(sizeof(*ret)); ret->notes = HashMapCreate(); ret->description = NULL; start = raw; while (!end_of_text) { char *line_content = NULL, *temporary = NULL; char *index_ptr; if (!(line_break = strchr(start, '\n'))) { end_of_text = true; /* Points to the EOF. I am using strchr because we need to * defer the {end_of_text}. */ line_break = start + strlen(start); } if (start >= line_break) { break; } if (!strncmp(start, " *", 2) || !strncmp(start, "*", 1)) { start += 1 + (*start == ' '); while (start && isspace(*start)) { start++; } } for (index_ptr = start; index_ptr < line_break; index_ptr++) { char char_buffer[2] = { *index_ptr, '\0' }; temporary = line_content; line_content = StrConcat(2, line_content, char_buffer); Free(temporary); } if (!line_content) { break; } if (!strncmp(line_content, "---", 3)) { parsing_notes = true; goto line_end; } if (parsing_notes) { char *del = strchr(line_content, ':'); char *val = del + 1; if (del && strlen(del) >= 1) { while (*val && isspace(*val)) { val++; } } if (del) { *del = '\0'; } HashMapSet(ret->notes, line_content, StrDuplicate(val)); goto line_end; } temporary = ret->description; ret->description = StrConcat(3, ret->description, line_content, "\n"); Free(temporary); line_end: Free(line_content); start = line_break + 1; } return ret; } static void GenerateReturns(Stream *out, AyadocComment *ayadoc, HeaderDeclaration decl, char *val) { if (StrEquals(val, "NOTHING")) { StreamPrintf(out, " nothing."); return; } StreamPrintf(out, ": "); /* TODO: Split all arguments by the '|', and handle them automatically * (with live-alongs, special capped params, ... */ StreamPrintf(out, " %s", val); } static void GenerateHTML(Stream *out, AyadocComment *ayadoc, HeaderDeclaration decl) { if (!out || !ayadoc) { return; } StreamPrintf(out, "
", decl.name); { StreamPrintf(out, "

%s


", decl.name); StreamPrintf(out, "

Signature

"); StreamPrintf(out, "
", decl.name); { char *attr, *value; StreamPrintf(out, ""); { StreamPrintf(out, ""); StreamPrintf(out, "%s", decl.returnType); StreamPrintf(out, "
"); } { size_t i, args = ArraySize(decl.args); StreamPrintf(out, ""); StreamPrintf(out, "%s", decl.name); StreamPrintf(out, ""); StreamPrintf(out, "("); for (i = 0; i < args; i++) { char *arg = ArrayGet(decl.args, i); StreamPrintf(out, ""); StreamPrintf(out, "%s", arg); StreamPrintf(out, ""); if (i != args - 1) { StreamPrintf(out, ", "); } } StreamPrintf(out, ");"); } StreamPrintf(out, "
"); StreamPrintf(out, "

Description

"); /* TODO: Pretty-print {}s by resolving from context. * We don't make the input HTML-safe, aside from newlines as * BR tags. */ StreamPrintf( out, "

%s

", ayadoc->description ? ayadoc->description : "" "(no description given)" "" ); /* Extra fields */ while (HashMapIterate(ayadoc->notes, &attr, (void **) &value)) { if (StrEquals(attr, "Returns")) { /* TODO: Be a little more advanced. */ StreamPrintf(out, "
", decl.name); StreamPrintf(out, "Returns"); GenerateReturns(out, ayadoc, decl, value); StreamPrintf(out, "
"); StreamFlush(out); continue; } } } StreamPrintf(out, "
"); } StreamPrintf(out, "
"); StreamFlush(out); } int Main(Array *args, HashMap *env) { ArgParseState state; int flag; char *header = NULL, *xhtml = NULL, *css = NULL; Stream *input, *output; ArgParseStateInit(&state); while ((flag = ArgParse(&state, args, "i:o:C:")) != -1) { switch (flag) { case 'i': header = state.optArg; break; case 'o': xhtml = state.optArg; break; case 'C': css = state.optArg; break; } } if (!header || !xhtml) { Log(LOG_ERR, "Usage: %s -i [C header file] -o [Generated Aya HTML]", ArrayGet(args, 0) ); return EXIT_FAILURE; } input = StreamOpen(header, "r"); output = StreamOpen(xhtml, "w"); StreamPrintf(output, ""); StreamPrintf(output, ""); StreamPrintf(output, ""); if (css) { Stream *css_stream = StreamOpen(css, "r"); StreamPrintf(output, ""); StreamClose(css_stream); } StreamPrintf(output, ""); StreamPrintf(output, ""); /* TODO */ { AyadocComment *comm = NULL; HeaderExpr expression = { 0 }; while (true) { HeaderDeclaration decl; HeaderParse(input, &expression); if (expression.type == HP_EOF) { break; } switch (expression.type) { case HP_COMMENT: if (strncmp(expression.data.text, "*", 1)) { break; } if (comm) { FreeAyadoc(comm); } comm = ParseAyadoc(expression.data.text); break; case HP_DECLARATION: if (!comm) { break; } decl = expression.data.declaration; GenerateHTML(output, comm, decl); FreeAyadoc(comm); comm = NULL; default: break; } } } StreamPrintf(output, ""); StreamPrintf(output, ""); StreamClose(output); StreamClose(input); return EXIT_SUCCESS; }