[ADD/WIP] Start making Ayadocs

Docs!!!
This commit is contained in:
LDA 2024-08-04 20:27:42 +02:00
commit c926397ed6
6 changed files with 414 additions and 3 deletions

339
tools/aya.c Normal file
View file

@ -0,0 +1,339 @@
/* 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 <Cytoplasm/HeaderParser.h>
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Stream.h>
#include <Cytoplasm/Args.h>
#include <Cytoplasm/Log.h>
#include <Cytoplasm/Str.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
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, " <span class='rets-none'>nothing.</span>");
return;
}
StreamPrintf(out, ": ");
/* TODO: Split all arguments by the '|', and handle them automatically
* (with live-alongs, special capped params, ... */
StreamPrintf(out, " <span class='rets-span'>%s</span>", val);
}
static void
GenerateHTML(Stream *out, AyadocComment *ayadoc, HeaderDeclaration decl)
{
if (!out || !ayadoc)
{
return;
}
StreamPrintf(out, "<div id='fdiv-%s'>", decl.name);
{
StreamPrintf(out, "<h2><code>%s</code></h2><hr/>", decl.name);
StreamPrintf(out, "<h3>Signature</h3>");
StreamPrintf(out, "<div id='sdiv-%s'>", decl.name);
{
char *attr, *value;
StreamPrintf(out, "<code>");
{
StreamPrintf(out, "<span class='aya-return'>");
StreamPrintf(out, "%s", decl.returnType);
StreamPrintf(out, "</span><br/>");
}
{
size_t i, args = ArraySize(decl.args);
StreamPrintf(out, "<span class='aya-name'>");
StreamPrintf(out, "<strong>%s</strong>", decl.name);
StreamPrintf(out, "</span>");
StreamPrintf(out, "(");
for (i = 0; i < args; i++)
{
char *arg = ArrayGet(decl.args, i);
StreamPrintf(out, "<span class='aya-arg'>");
StreamPrintf(out, "%s", arg);
StreamPrintf(out, "</span>");
if (i != args - 1)
{
StreamPrintf(out, ", ");
}
}
StreamPrintf(out, ");");
}
StreamPrintf(out, "</code>");
StreamPrintf(out, "<h3>Description</h3>");
/* TODO: Pretty-print {}s by resolving from context.
* We don't make the input HTML-safe, aside from newlines as
* BR tags. */
StreamPrintf(
out,
"<p class='aya-desc'>%s</p>",
ayadoc->description ?
ayadoc->description :
"<span class='aya-ndesc'>"
"(no description given)"
"</span>"
);
/* Extra fields */
while (HashMapIterate(ayadoc->notes, &attr, (void **) &value))
{
if (StrEquals(attr, "Returns"))
{
/* TODO: Be a little more advanced. */
StreamPrintf(out, "<div class='aya-pret' id='pret-%s'>", decl.name);
StreamPrintf(out, "Returns");
GenerateReturns(out, ayadoc, decl, value);
StreamPrintf(out, "</div>");
StreamFlush(out);
continue;
}
}
}
StreamPrintf(out, "</div>");
}
StreamPrintf(out, "</div>");
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, "<!DOCTYPE html>");
StreamPrintf(output, "<html>");
StreamPrintf(output, "<head>");
if (css)
{
Stream *css_stream = StreamOpen(css, "r");
StreamPrintf(output, "<style>");
StreamCopy(css_stream, output);
StreamPrintf(output, "</style>");
StreamClose(css_stream);
}
StreamPrintf(output, "</head>");
StreamPrintf(output, "<body>");
/* 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, "</body>");
StreamPrintf(output, "</html>");
StreamClose(output);
StreamClose(input);
return EXIT_SUCCESS;
}