/* 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 #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++; } } line_content = StrDuplicate(""); 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 (!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 HandleReturnValue(Stream *out, AyadocComment *aya, HeaderDeclaration d, char *field) { char *tag_del = NULL; if (StrEquals(field, "NOTHING")) { StreamPrintf(out, "nothing"); return; } else if (StrEquals(field, "NORETURN")) { StreamPrintf(out, "doesn't return in the thread"); return; } if ((tag_del = strchr(field, '['))) { /* We're free to edit field, as it is in the heap */ *tag_del = '\0'; } /* Already write the field */ StreamPrintf(out, " %s", field); if (tag_del) { /* Locate the last ]. */ char *end_tags = strrchr(++tag_del, ']'); if (!end_tags) { end_tags = tag_del + strlen(tag_del); } *end_tags = '\0'; if (StrEquals(tag_del, "LA:HEAP") || StrEquals(tag_del, "HEAP")) { StreamPrintf(out, " (stored in the heap)"); return; } else if (!strncmp(tag_del, "LA:", 3)) { tag_del += 3; } StreamPrintf(out, " (stored along %s)", tag_del); } } static void HandleSeeValue(Stream *out, AyadocComment *aya, HeaderDeclaration d, char *field) { Uri *uri = UriParse(field); if (uri) { StreamPrintf(out, "%s", field, uri->host ); UriFree(uri); return; } StreamPrintf(out, "%s", field, field ); } #define SplitBy(sym, cb, del_str) \ char *start_of_arg = val, *separator = NULL; \ bool end_of_field = false; \ \ while (!end_of_field) \ { \ char *temporary, *field = NULL, *index; \ char *last_char; \ while (*start_of_arg && isspace(*start_of_arg)) \ { \ start_of_arg++; \ } \ if (!(separator = strchr(start_of_arg, sym))) \ { \ end_of_field = true; \ separator = start_of_arg + strlen(start_of_arg); \ } \ if (start_of_arg >= separator) \ { \ break; \ } \ \ /* Trim out spaces */ \ last_char = separator - 1; \ while (*last_char && isspace(*last_char)) \ { \ last_char--; \ } \ \ field = StrDuplicate(""); \ for (index = start_of_arg; index <= last_char; index++) \ { \ char buffer[2] = { *index, '\0' }; \ \ temporary = field; \ field = StrConcat(2, field, buffer); \ Free(temporary); \ } \ \ /* Process the field */ \ cb(out, ayadoc, decl, field); \ \ if (!end_of_field) \ { \ StreamPrintf(out, del_str); \ } \ \ Free(field); \ start_of_arg = separator + 1; \ } static void GenerateReturns(Stream *out, AyadocComment *ayadoc, HeaderDeclaration decl, char *val) { SplitBy('|', HandleReturnValue, " or "); } static void GenerateSee(Stream *out, AyadocComment *ayadoc, HeaderDeclaration decl, char *val) { SplitBy(',', HandleSeeValue, ", "); } #undef SplitBy static void GenerateHTML(Stream *out, AyadocComment *ayadoc, HeaderDeclaration decl) { if (!out || !ayadoc) { return; } StreamPrintf(out, "
", decl.name); { StreamPrintf(out, "
"); 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")) { StreamPrintf(out, "
", decl.name); StreamPrintf(out, "

Returns

"); GenerateReturns(out, ayadoc, decl, value); StreamPrintf(out, "
"); StreamFlush(out); continue; } else if (StrEquals(attr, "See-Also")) { StreamPrintf(out, "
", decl.name); StreamPrintf(out, "

See also

"); GenerateSee(out, ayadoc, decl, value); StreamPrintf(out, "
"); StreamFlush(out); continue; } else if (StrEquals(attr, "Thrasher")) { StreamPrintf(out, "

", decl.name); StreamPrintf(out, "This function may be destroyed with "); StreamPrintf(out, "%s", value, 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; char *project = "Ayaya!"; Stream *input, *output; bool help = false; ArgParseStateInit(&state); while ((flag = ArgParse(&state, args, "i:o:p:C:h")) != -1) { switch (flag) { case 'i': header = state.optArg; break; case 'o': xhtml = state.optArg; break; case 'C': css = state.optArg; break; case 'p': project = state.optArg; break; case 'h': help = true; break; } } if (!header || !xhtml || help) { Log(help ? LOG_INFO : LOG_ERR, "Usage: %s " "-i [C header file] " "-o [Generated Aya HTML] " "-p {project name} " "-C {CSS stylefile}", 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, ""); StreamPrintf(output, "

%s: %s

", project, header); StreamPrintf(output, "
"); /* TODO */ { AyadocComment *comm = NULL; HeaderExpr expression = { 0 }; while (true) { HeaderDeclaration decl; bool raw_put = false; HeaderParse(input, &expression); if (expression.type == HP_EOF) { break; } switch (expression.type) { case HP_COMMENT: if (strncmp(expression.data.text, "*", 1) && strncmp(expression.data.text, "-*", 2)) { break; } if (!strncmp(expression.data.text, "-*", 2)) { raw_put = true; } if (comm) { FreeAyadoc(comm); } comm = ParseAyadoc(expression.data.text + !!raw_put); if (raw_put) { StreamPrintf(output, "

"); StreamPrintf(output, comm->description); StreamPrintf(output, "

"); FreeAyadoc(comm); comm = NULL; } 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; }