#define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include // --- 1. DATA STRUCTURES --- typedef enum { KIND_FUNCTION, KIND_STRUCT, KIND_ENUM, KIND_TYPEDEF } ItemKind; typedef struct { char* name; char* type; // For Structs: type name. For Enums: value. char* doc; } Field; typedef struct { char* name; char* doc_comment; ItemKind kind; char* return_type; char* underlying_type; Field* args; Field* fields; int arg_count; int field_count; char* source_file; char* anchor_id; } DocItem; typedef struct { DocItem* items; size_t count; size_t capacity; char* filename; char* file_doc; } FileContext; typedef struct { FileContext* files; size_t count; size_t capacity; DocItem** registry; size_t reg_count; size_t reg_cap; } ProjectContext; // --- 2. HELPERS --- char* my_strdup(const char* s) { if (!s) return NULL; size_t len = strlen(s); char* d = malloc(len + 1); if (d) memcpy(d, s, len + 1); return d; } char* to_cstr(CXString cx_str) { const char* temp = clang_getCString(cx_str); char* result = temp ? my_strdup(temp) : my_strdup(""); clang_disposeString(cx_str); return result; } const char* get_filename(const char* path) { const char* last_slash = strrchr(path, '/'); const char* last_backslash = strrchr(path, '\\'); const char* filename = path; if (last_slash && last_slash > filename) filename = last_slash + 1; if (last_backslash && last_backslash > filename) filename = last_backslash + 1; return filename; } void init_project(ProjectContext* proj) { proj->count = 0; proj->capacity = 10; proj->files = malloc(sizeof(FileContext) * proj->capacity); proj->reg_count = 0; proj->reg_cap = 100; proj->registry = malloc(sizeof(DocItem*) * proj->reg_cap); } FileContext* add_file(ProjectContext* proj, const char* filepath) { if (proj->count >= proj->capacity) { proj->capacity *= 2; proj->files = realloc(proj->files, sizeof(FileContext) * proj->capacity); } FileContext* ctx = &proj->files[proj->count++]; ctx->filename = my_strdup(get_filename(filepath)); ctx->file_doc = NULL; ctx->count = 0; ctx->capacity = 32; ctx->items = malloc(sizeof(DocItem) * ctx->capacity); return ctx; } void register_item(ProjectContext* proj, DocItem* item) { if (proj->reg_count >= proj->reg_cap) { proj->reg_cap *= 2; proj->registry = realloc(proj->registry, sizeof(DocItem*) * proj->reg_cap); } proj->registry[proj->reg_count++] = item; } DocItem* add_item(FileContext* ctx) { if (ctx->count >= ctx->capacity) { ctx->capacity *= 2; ctx->items = realloc(ctx->items, sizeof(DocItem) * ctx->capacity); } DocItem* item = &ctx->items[ctx->count++]; memset(item, 0, sizeof(DocItem)); item->source_file = ctx->filename; return item; } int item_exists(FileContext* ctx, const char* name) { if (!name) return 0; for (size_t i = 0; i < ctx->count; i++) { if (ctx->items[i].name && strcmp(ctx->items[i].name, name) == 0) { return 1; } } return 0; } // --- 3. LINK RESOLUTION --- // --- 3. LINK RESOLUTION --- // NEW: Helper to find any symbol (Item, Enum Variant, or Struct.Field) // Returns 1 if found, setting out_file and out_anchor. 0 otherwise. int find_link_target(ProjectContext* proj, const char* name, const char** out_file, const char** out_anchor) { if (!name) return 0; for (size_t i = 0; i < proj->reg_count; i++) { DocItem* item = proj->registry[i]; // 1. Check Top-Level Item Name (Struct, Enum, Function, Typedef) if (item->name && strcmp(item->name, name) == 0) { *out_file = item->source_file; *out_anchor = item->anchor_id; return 1; } // 2. Check Enum Variants (Global Scope in C) // Allows linking to [`SOME_ENUM_VALUE`] if (item->kind == KIND_ENUM) { for (int j = 0; j < item->field_count; j++) { if (item->fields[j].name && strcmp(item->fields[j].name, name) == 0) { *out_file = item->source_file; *out_anchor = item->fields[j].name; // Anchor is the variant name return 1; } } } // 3. Check Struct Fields (Scoped: StructName.FieldName) // Allows linking to [`MyStruct.my_field`] if (item->kind == KIND_STRUCT && item->name) { size_t len = strlen(item->name); if (strncmp(name, item->name, len) == 0 && name[len] == '.') { const char* field_part = name + len + 1; for (int j = 0; j < item->field_count; j++) { if (item->fields[j].name && strcmp(item->fields[j].name, field_part) == 0) { *out_file = item->source_file; *out_anchor = item->fields[j].name; return 1; } } } } } return 0; } // Kept for internal logic if needed, but mostly replaced by find_link_target DocItem* find_item(ProjectContext* proj, const char* name) { if (!name) return NULL; for (size_t i = 0; i < proj->reg_count; i++) { if (proj->registry[i]->name && strcmp(proj->registry[i]->name, name) == 0) { return proj->registry[i]; } } return NULL; } int is_ident_char(char c) { return isalnum((unsigned char)c) || c == '_'; } char* linkify_type(ProjectContext* proj, const char* raw_type, const char* current_file) { if (!raw_type) return NULL; size_t cap = strlen(raw_type) * 3 + 256; char* out = malloc(cap); size_t out_len = 0; out[0] = '\0'; const char* p = raw_type; char word[256]; int w_idx = 0; while (*p) { if (is_ident_char(*p)) { if (w_idx < 255) word[w_idx++] = *p; } else { if (w_idx > 0) { word[w_idx] = '\0'; const char *target_file, *target_anchor; if (find_link_target(proj, word, &target_file, &target_anchor)) { // Check if same file for relative link if (current_file && strcmp(target_file, current_file) == 0) { out_len += snprintf(out + out_len, cap - out_len, "%s", target_anchor, word); } else { out_len += snprintf(out + out_len, cap - out_len, "%s", target_file, target_anchor, word); } } else { out_len += snprintf(out + out_len, cap - out_len, "%s", word); } w_idx = 0; } if (out_len < cap - 1) { out[out_len++] = *p; out[out_len] = '\0'; } } p++; } if (w_idx > 0) { word[w_idx] = '\0'; const char *target_file, *target_anchor; if (find_link_target(proj, word, &target_file, &target_anchor)) { if (current_file && strcmp(target_file, current_file) == 0) { out_len += snprintf(out + out_len, cap - out_len, "%s", target_anchor, word); } else { out_len += snprintf(out + out_len, cap - out_len, "%s", target_file, target_anchor, word); } } else { out_len += snprintf(out + out_len, cap - out_len, "%s", word); } } return out; } char* resolve_links(ProjectContext* proj, const char* text, const char* current_file) { if (!text) return NULL; size_t cap = strlen(text) * 2 + 512; char* output = malloc(cap); size_t out_len = 0; const char* p = text; while (*p) { if (p[0] == '[' && p[1] == '`') { const char* start = p + 2; const char* end = strstr(start, "`]"); if (end) { int name_len = end - start; char name[128]; if (name_len < 127) { strncpy(name, start, name_len); name[name_len] = '\0'; const char *target_file, *target_anchor; if (find_link_target(proj, name, &target_file, &target_anchor)) { if (current_file && strcmp(target_file, current_file) == 0) { out_len += sprintf(output + out_len, "[`%s`](#%s)", name, target_anchor); } else { out_len += sprintf(output + out_len, "[`%s`](%s.html#%s)", name, target_file, target_anchor); } p = end + 2; continue; } } } } output[out_len++] = *p++; if (out_len >= cap - 100) { cap *= 2; output = realloc(output, cap); } } output[out_len] = '\0'; return output; } char* clean_comment(const char* raw) { if (!raw) return NULL; size_t len = strlen(raw); char* output = malloc(len + 1); char* out_ptr = output; char* temp = my_strdup(raw); char* line = strtok(temp, "\n"); while (line != NULL) { char* p = line; while (*p && isspace((unsigned char)*p)) p++; if (strncmp(p, "/**", 3) == 0) p += 3; else if (strncmp(p, "/*!", 3) == 0) p += 3; else if (strncmp(p, "///", 3) == 0) p += 3; else if (strncmp(p, "*/", 2) == 0) { line = strtok(NULL, "\n"); continue; } else if (*p == '*') p++; if (*p == ' ') p++; while (*p) *out_ptr++ = *p++; *out_ptr++ = '\n'; line = strtok(NULL, "\n"); } *out_ptr = '\0'; free(temp); return output; } // --- 4. PARSING --- void parse_file_level_docs(FileContext* ctx, const char* real_path) { FILE* f = fopen(real_path, "r"); if (!f) return; char* buffer = malloc(50000); if (!buffer) { fclose(f); return; } buffer[0] = '\0'; char line[2048]; while (fgets(line, sizeof(line), f)) { char* p = line; while (*p && isspace((unsigned char)*p)) p++; if (strncmp(p, "//!", 3) == 0) { p += 3; if (*p == ' ') p++; strcat(buffer, p); } } fclose(f); if (strlen(buffer) > 0) ctx->file_doc = buffer; else free(buffer); } int is_skippable(CXCursor cursor) { if (clang_Cursor_isAnonymous(cursor)) return 1; CXString cx = clang_getCursorSpelling(cursor); const char* s = clang_getCString(cx); int skip = (!s || strlen(s) == 0 || strstr(s, "(unnamed") != NULL); clang_disposeString(cx); return skip; } enum CXChildVisitResult struct_visitor(CXCursor cursor, CXCursor parent, CXClientData client_data) { DocItem* item = (DocItem*)client_data; if (clang_getCursorKind(cursor) == CXCursor_FieldDecl) { int idx = item->field_count++; item->fields = realloc(item->fields, sizeof(Field) * item->field_count); item->fields[idx].name = to_cstr(clang_getCursorSpelling(cursor)); item->fields[idx].type = to_cstr(clang_getTypeSpelling(clang_getCursorType(cursor))); item->fields[idx].doc = to_cstr(clang_Cursor_getRawCommentText(cursor)); } return CXChildVisit_Continue; } enum CXChildVisitResult enum_visitor(CXCursor cursor, CXCursor parent, CXClientData client_data) { DocItem* item = (DocItem*)client_data; if (clang_getCursorKind(cursor) == CXCursor_EnumConstantDecl) { int idx = item->field_count++; item->fields = realloc(item->fields, sizeof(Field) * item->field_count); item->fields[idx].name = to_cstr(clang_getCursorSpelling(cursor)); long long val = clang_getEnumConstantDeclValue(cursor); char val_str[64]; snprintf(val_str, 64, "%lld", val); item->fields[idx].type = my_strdup(val_str); item->fields[idx].doc = to_cstr(clang_Cursor_getRawCommentText(cursor)); } return CXChildVisit_Continue; } enum CXChildVisitResult typedef_param_visitor(CXCursor cursor, CXCursor parent, CXClientData client_data) { DocItem* item = (DocItem*)client_data; if (clang_getCursorKind(cursor) == CXCursor_ParmDecl) { int idx = item->arg_count++; item->args = realloc(item->args, sizeof(Field) * item->arg_count); item->args[idx].name = to_cstr(clang_getCursorSpelling(cursor)); item->args[idx].type = to_cstr(clang_getTypeSpelling(clang_getCursorType(cursor))); item->args[idx].doc = NULL; } return CXChildVisit_Continue; } enum CXChildVisitResult main_visitor(CXCursor cursor, CXCursor parent, CXClientData client_data) { void** args = (void**)client_data; FileContext* ctx = (FileContext*)args[0]; ProjectContext* proj = (ProjectContext*)args[1]; CXSourceLocation location = clang_getCursorLocation(cursor); if (clang_Location_isFromMainFile(location) == 0) return CXChildVisit_Continue; enum CXCursorKind kind = clang_getCursorKind(cursor); DocItem* item = NULL; if (kind == CXCursor_FunctionDecl) { char* name = to_cstr(clang_getCursorSpelling(cursor)); if (item_exists(ctx, name)) { free(name); return CXChildVisit_Continue; } item = add_item(ctx); item->kind = KIND_FUNCTION; item->name = name; item->doc_comment = to_cstr(clang_Cursor_getRawCommentText(cursor)); item->return_type = to_cstr(clang_getTypeSpelling(clang_getCursorResultType(cursor))); char buf[256]; snprintf(buf, 256, "fn.%s", item->name ? item->name : "unknown"); item->anchor_id = my_strdup(buf); int num = clang_Cursor_getNumArguments(cursor); item->arg_count = num; item->args = malloc(sizeof(Field) * num); for (int i=0; iargs[i].name = to_cstr(clang_getCursorSpelling(arg)); item->args[i].type = to_cstr(clang_getTypeSpelling(clang_getCursorType(arg))); item->args[i].doc = NULL; } } else if (kind == CXCursor_StructDecl) { if (is_skippable(cursor) || !clang_isCursorDefinition(cursor)) return CXChildVisit_Continue; char* name = to_cstr(clang_getCursorSpelling(cursor)); if (item_exists(ctx, name)) { free(name); return CXChildVisit_Continue; } item = add_item(ctx); item->kind = KIND_STRUCT; item->name = name; item->doc_comment = to_cstr(clang_Cursor_getRawCommentText(cursor)); char buf[256]; snprintf(buf, 256, "struct.%s", item->name ? item->name : "unknown"); item->anchor_id = my_strdup(buf); clang_visitChildren(cursor, struct_visitor, item); } else if (kind == CXCursor_EnumDecl) { if (is_skippable(cursor) || !clang_isCursorDefinition(cursor)) return CXChildVisit_Continue; char* name = to_cstr(clang_getCursorSpelling(cursor)); if (item_exists(ctx, name)) { free(name); return CXChildVisit_Continue; } item = add_item(ctx); item->kind = KIND_ENUM; item->name = name; item->doc_comment = to_cstr(clang_Cursor_getRawCommentText(cursor)); char buf[256]; snprintf(buf, 256, "enum.%s", item->name ? item->name : "unknown"); item->anchor_id = my_strdup(buf); clang_visitChildren(cursor, enum_visitor, item); } else if (kind == CXCursor_TypedefDecl) { char* name = to_cstr(clang_getCursorSpelling(cursor)); if (item_exists(ctx, name)) { free(name); return CXChildVisit_Continue; } item = add_item(ctx); item->name = name; CXType underlying = clang_getTypedefDeclUnderlyingType(cursor); CXType canonical = clang_getCanonicalType(underlying); if (canonical.kind == CXType_Record) { item->kind = KIND_STRUCT; char buf[256]; snprintf(buf, 256, "struct.%s", item->name ? item->name : "unknown"); item->anchor_id = my_strdup(buf); char* doc = to_cstr(clang_Cursor_getRawCommentText(cursor)); if (!doc || !*doc) { if(doc) free(doc); CXCursor sc = clang_getTypeDeclaration(canonical); doc = to_cstr(clang_Cursor_getRawCommentText(sc)); } item->doc_comment = doc; CXCursor sc = clang_getTypeDeclaration(canonical); clang_visitChildren(sc, struct_visitor, item); } else if (canonical.kind == CXType_Enum) { item->kind = KIND_ENUM; char buf[256]; snprintf(buf, 256, "enum.%s", item->name ? item->name : "unknown"); item->anchor_id = my_strdup(buf); char* doc = to_cstr(clang_Cursor_getRawCommentText(cursor)); if (!doc || !*doc) { if(doc) free(doc); CXCursor sc = clang_getTypeDeclaration(canonical); doc = to_cstr(clang_Cursor_getRawCommentText(sc)); } item->doc_comment = doc; CXCursor sc = clang_getTypeDeclaration(canonical); clang_visitChildren(sc, enum_visitor, item); } else { item->kind = KIND_TYPEDEF; char buf[256]; snprintf(buf, 256, "type.%s", item->name ? item->name : "unknown"); item->anchor_id = my_strdup(buf); item->doc_comment = to_cstr(clang_Cursor_getRawCommentText(cursor)); item->underlying_type = to_cstr(clang_getTypeSpelling(underlying)); // UPDATED: Check if it's a function pointer and extract params if (underlying.kind == CXType_Pointer) { CXType pointee = clang_getPointeeType(underlying); if (pointee.kind == CXType_FunctionProto) { item->return_type = to_cstr(clang_getTypeSpelling(clang_getResultType(pointee))); // Visit children to find ParmDecl parameters clang_visitChildren(cursor, typedef_param_visitor, item); } } } } if (item) register_item(proj, item); return CXChildVisit_Recurse; } // --- 5. HTML GENERATION --- void render_md(FILE* f, ProjectContext* proj, const char* raw, const char* current_file) { if (!raw) return; char* clean = clean_comment(raw); char* linked = resolve_links(proj, clean, current_file); char* html = cmark_markdown_to_html(linked, strlen(linked), CMARK_OPT_UNSAFE); fprintf(f, "%s", html); free(html); free(linked); free(clean); } void write_common_head(FILE* f, const char* title) { fprintf(f, ""); fprintf(f, "%s", title); fprintf(f, ""); fprintf(f, ""); } int compare_item_ptrs(const void* a, const void* b) { const DocItem* da = *(const DocItem**)a; const DocItem* db = *(const DocItem**)b; if (!da->name && !db->name) return 0; if (!da->name) return 1; if (!db->name) return -1; return strcmp(da->name, db->name); } void render_sidebar_section(FILE* f, FileContext* ctx, ItemKind kind, const char* title) { size_t count = 0; // Count items of this kind for(size_t i=0; icount; i++) { if (ctx->items[i].kind == kind) { count++; } } if (count == 0) return; // Create a temporary array of pointers DocItem** ptrs = malloc(sizeof(DocItem*) * count); size_t idx = 0; for(size_t i=0; icount; i++) { if (ctx->items[i].kind == kind) { ptrs[idx++] = &ctx->items[i]; } } // Sort the pointers alphabetically for the sidebar qsort(ptrs, count, sizeof(DocItem*), compare_item_ptrs); // Render sorted links fprintf(f, "

%s

", title); for(size_t i=0; i%s", ptrs[i]->anchor_id, ptrs[i]->name); } free(ptrs); } void generate_file_html(ProjectContext* proj, FileContext* ctx, const char* out_dir) { char path[1024]; snprintf(path, 1024, "%s/%s.html", out_dir, ctx->filename); FILE* f = fopen(path, "w"); if (!f) return; write_common_head(f, ctx->filename); // --- SIDEBAR GENERATION --- fprintf(f, ""); // -------------------------- fprintf(f, "
"); fprintf(f, "

Header %s

", ctx->filename); if (ctx->file_doc) { fprintf(f, "
"); render_md(f, proj, ctx->file_doc, ctx->filename); fprintf(f, "
"); } for(size_t i=0; icount; i++) { DocItem* item = &ctx->items[i]; const char* kind_str = "Unknown"; if (item->kind == KIND_FUNCTION) kind_str = "Function"; else if (item->kind == KIND_STRUCT) kind_str = "Struct"; else if (item->kind == KIND_ENUM) kind_str = "Enum"; else if (item->kind == KIND_TYPEDEF) kind_str = "Type Alias"; fprintf(f, "

%s %s

", item->anchor_id, kind_str, item->anchor_id, item->name); fprintf(f, "
"); if (item->kind == KIND_FUNCTION) { char* ret_linked = linkify_type(proj, item->return_type, ctx->filename); fprintf(f, "%s %s(", ret_linked, item->name); free(ret_linked); for(int j=0; jarg_count; j++) { char* arg_linked = linkify_type(proj, item->args[j].type, ctx->filename); fprintf(f, "\n %s %s", arg_linked, item->args[j].name); free(arg_linked); if(jarg_count-1) fprintf(f, ","); } fprintf(f, "\n)"); } else if (item->kind == KIND_STRUCT) { fprintf(f, "struct %s {", item->name); for(int j=0; jfield_count; j++) { char* f_linked = linkify_type(proj, item->fields[j].type, ctx->filename); fprintf(f, "\n %s %s;", f_linked, item->fields[j].name); free(f_linked); } fprintf(f, "\n}"); } else if (item->kind == KIND_ENUM) { fprintf(f, "enum %s {", item->name); for(int j=0; jfield_count; j++) { fprintf(f, "\n %s = %s,", item->fields[j].name, item->fields[j].type); } fprintf(f, "\n}"); } else { if (item->return_type && item->arg_count > 0) { char* ret_linked = linkify_type(proj, item->return_type, ctx->filename); fprintf(f, "typedef %s = %s (*)(", item->name, ret_linked); free(ret_linked); for(int j=0; jarg_count; j++) { char* arg_linked = linkify_type(proj, item->args[j].type, ctx->filename); fprintf(f, "%s %s", arg_linked, item->args[j].name); free(arg_linked); if(j < item->arg_count - 1) fprintf(f, ", "); } fprintf(f, ");"); } else { char* under_linked = linkify_type(proj, item->underlying_type, ctx->filename); fprintf(f, "typedef %s = %s;", item->name, under_linked); free(under_linked); } } fprintf(f, "
"); if (item->doc_comment) { fprintf(f, "
"); render_md(f, proj, item->doc_comment, ctx->filename); fprintf(f, "
"); } if ((item->kind == KIND_STRUCT || item->kind == KIND_ENUM) && item->field_count > 0) { int has_field_docs = 0; for(int k=0; kfield_count; k++) if(item->fields[k].doc) has_field_docs=1; if(has_field_docs) { fprintf(f, "

%s

", item->kind == KIND_ENUM ? "Variants" : "Fields"); for(int j=0; jfield_count; j++) { if(item->fields[j].doc) { fprintf(f, "
", item->fields[j].name); fprintf(f, "%s", item->fields[j].name); fprintf(f, "
"); render_md(f, proj, item->fields[j].doc, ctx->filename); fprintf(f, "
"); } } } } } fprintf(f, "
"); fclose(f); } void generate_index(ProjectContext* proj, const char* out_dir) { char path[1024]; snprintf(path, 1024, "%s/index.html", out_dir); FILE* f = fopen(path, "w"); if (!f) return; write_common_head(f, "Project Documentation"); fprintf(f, ""); fprintf(f, "
"); fprintf(f, "

Project Documentation

"); fprintf(f, "

Headers

    "); for(size_t i=0; icount; i++) { fprintf(f, "
  • %s
  • ", proj->files[i].filename, proj->files[i].filename); } fprintf(f, "
"); fprintf(f, "

Global Symbols

"); for(size_t i=0; ireg_count; i++) { DocItem* item = proj->registry[i]; if (item->name) { fprintf(f, "%s", item->source_file, item->anchor_id, item->name); } } fprintf(f, "
"); fprintf(f, "
"); fclose(f); } int dir_exists(const char* path) { struct stat sb; return stat(path, &sb) == 0 && S_ISDIR(sb.st_mode); } int compare_items(const void* a, const void* b) { const DocItem* da = (const DocItem*)a; const DocItem* db = (const DocItem*)b; // Safety checks for NULL names if (!da->name && !db->name) return 0; if (!da->name) return 1; if (!db->name) return -1; return strcmp(da->name, db->name); } int main(int argc, char** argv) { #ifndef _WIN32 signal(SIGPIPE, SIG_IGN); #endif if (argc < 3) { printf("Usage: %s [file2.h ...]\n", argv[0]); return 1; } const char* out_dir = argv[1]; char clang_inc_path[1024] = {0}; int found_inc = 0; FILE* p = popen("clang -print-resource-dir", "r"); if (p) { if (fgets(clang_inc_path, sizeof(clang_inc_path), p)) { size_t len = strlen(clang_inc_path); while(len > 0 && isspace((unsigned char)clang_inc_path[len-1])) clang_inc_path[--len] = '\0'; strcat(clang_inc_path, "/include"); if (dir_exists(clang_inc_path)) found_inc = 1; } pclose(p); } if (!found_inc) { const char* common_paths[] = { "/usr/lib/clang/18/include", "/usr/lib/clang/17/include", "/usr/lib/clang/16/include", "/usr/lib/clang/15/include", "/usr/lib/clang/14/include", "/usr/lib64/clang/18/include", NULL }; for (int i = 0; common_paths[i]; i++) { if (dir_exists(common_paths[i])) { strcpy(clang_inc_path, common_paths[i]); found_inc = 1; break; } } } char arg_include_flag[1100]; const char* clang_args[8]; int num_args = 0; clang_args[num_args++] = "-I."; clang_args[num_args++] = "-Iinclude"; clang_args[num_args++] = "-xc"; if (found_inc) { snprintf(arg_include_flag, sizeof(arg_include_flag), "-I%s", clang_inc_path); clang_args[num_args++] = arg_include_flag; printf("Using Clang headers: %s\n", clang_inc_path); } ProjectContext proj; init_project(&proj); CXIndex index = clang_createIndex(0, 1); for (int i = 2; i < argc; i++) { const char* filepath = argv[i]; printf("Parsing %s...\n", filepath); FileContext* file_ctx = add_file(&proj, filepath); parse_file_level_docs(file_ctx, filepath); CXTranslationUnit unit = clang_parseTranslationUnit( index, filepath, clang_args, num_args, NULL, 0, CXTranslationUnit_None ); if (!unit) { printf("Failed to parse %s\n", filepath); continue; } CXCursor root = clang_getTranslationUnitCursor(unit); void* args[] = { file_ctx, &proj }; clang_visitChildren(root, main_visitor, args); clang_disposeTranslationUnit(unit); } printf("Generating HTML in '%s'...\n", out_dir); #ifdef _WIN32 _mkdir(out_dir); #else mkdir(out_dir, 0777); #endif for (size_t i = 0; i < proj.count; i++) { generate_file_html(&proj, &proj.files[i], out_dir); } generate_index(&proj, out_dir); clang_disposeIndex(index); printf("Done! Open %s/index.html\n", out_dir); return 0; }