#define USE_SINGLE_HEADER 1 #define DN_H_WITH_OS 1 #define DN_H_WITH_CORE 1 #if USE_SINGLE_HEADER #include "Single-Header/dn_single_header.h" #else #include "Source/dn.h" #endif #if USE_SINGLE_HEADER #include "Single-Header/dn_single_header.cpp" #else #include "Source/dn.cpp" #endif enum FileType { FileType_Header, FileType_Impl, FileType_Count }; struct File { FileType type; DN_Str8 file_name; }; static void AppendCppFileLineByLine(DN_Str8Builder *dest, DN_Str8 cpp_path) { DN_TCScratch scratch = DN_TCScratchBegin(&dest->arena, 1); DN_ErrSink *err = DN_TCErrSinkBeginDefault(); DN_Str8 buffer = DN_OS_FileReadAllArena(&scratch.arena, cpp_path, err); DN_ErrSinkEndExitIfErrorF(err, -1, "Failed to load file from '%S' for appending", cpp_path); bool inside_clangd_preprocessor_block = false; for (DN_Str8 inc_walker = buffer;;) { DN_Str8BSplitResult split = DN_Str8BSplit(inc_walker, DN_Str8Lit("\n")); if (split.lhs.size == 0) break; inc_walker = split.rhs; // NOTE: Trim the whitespace, mainly for windows, the file we read will have \r\n whereas we just want to emit \n DN_Str8 line = DN_Str8TrimTailWhitespace(split.lhs); // NOTE: Detect if we're inside a clangd preprocessor block // TODO: This breaks if there's any nested #if's in the block (we naiively match on #endif) bool commented_out = false; if (!inside_clangd_preprocessor_block) { DN_Str8FindResult find = DN_Str8FindStr8(line, DN_Str8Lit("#if defined(_CLANGD)"), DN_Str8EqCase_Sensitive); if (find.found) { line = DN_Str8FromFmtArena(&scratch.arena, "%S// DN: Single header generator commented out => %S", find.start_to_before_match, DN_Str8TrimWhitespaceAround(find.match_to_end_of_buffer)); commented_out = true; inside_clangd_preprocessor_block = true; } } // NOTE: Inline relative includes as long as we're not inside a CLANDG block DN_Str8 extra_include_path = {}; if (!inside_clangd_preprocessor_block) { DN_Str8 ignore_patterns[] = { DN_Str8Lit("#include \"dn.h\""), DN_Str8Lit("#include \"dn.cpp\""), }; DN_Str8FindResult ignore = DN_Str8FindStr8Array(line, ignore_patterns, DN_ArrayCountU(ignore_patterns), DN_Str8EqCase_Sensitive); if (!ignore.found) { DN_Str8FindResult find = DN_Str8FindStr8(line, DN_Str8Lit("#include \""), DN_Str8EqCase_Sensitive); if (find.found) { line = DN_Str8FromFmtArena(&scratch.arena, "%S// DN: Single header generator commented out => %S", find.start_to_before_match, DN_Str8TrimWhitespaceAround(find.match_to_end_of_buffer)); DN_Str8 rel_include_path = DN_Str8TrimWhitespaceAround(find.after_match_to_end_of_buffer); DN_Str8 root_dir = DN_Str8FileDirectoryFromPath(cpp_path); extra_include_path = DN_OS_PathF(&scratch.arena, "%S/%S", root_dir, DN_Str8TrimSuffix(rel_include_path, DN_Str8Lit("\""))); } } } // NOTE: Detect if we're at the end of the CLAND block if (inside_clangd_preprocessor_block) { if (!commented_out) line = DN_Str8FromFmtArena(&scratch.arena, "// %S", line); if (DN_Str8FindStr8(line, DN_Str8Lit("#endif"), DN_Str8EqCase_Sensitive).found) inside_clangd_preprocessor_block = false; } DN_Str8BuilderAppendCopy(dest, line); DN_Str8BuilderAppendRef(dest, DN_Str8Lit("\n")); if (extra_include_path.size) AppendCppFileLineByLine(dest, extra_include_path); } DN_TCScratchEnd(&scratch); } int main(int argc, char **argv) { DN_Core dn = {}; DN_Init(&dn, DN_InitFlags_OS | DN_InitFlags_ThreadContext, nullptr); if (argc != 3) { DN_OS_PrintErrF("USAGE: %s ", argv[0]); return -1; } DN_Str8 dn_root_dir = DN_Str8FromCStr8(argv[1]); DN_Str8 output_dir = DN_Str8FromCStr8(argv[2]); if (!DN_OS_PathMakeDir(output_dir)) { DN_OS_PrintErrF("Failed to make requested output directory: %S", output_dir); return -1; } DN_Str8 const REL_FILE_PATHS[] = { DN_Str8Lit("dn"), DN_Str8Lit("Extra/dn_async"), DN_Str8Lit("Extra/dn_bin_pack"), DN_Str8Lit("Extra/dn_csv"), DN_Str8Lit("Extra/dn_helpers"), }; for (DN_ForIndexU(type, FileType_Count)) { DN_TCScratch scratch = DN_TCScratchBegin(nullptr, 0); DN_Str8Builder builder = DN_Str8BuilderFromArena(&scratch.arena); DN_Str8 suffix = type == FileType_Header ? DN_Str8Lit("h") : DN_Str8Lit("cpp"); for (DN_ForItCArray(extra_it, DN_Str8 const, REL_FILE_PATHS)) { DN_Str8 extra_path = DN_OS_PathF(&scratch.arena, "%S/%S.%S", dn_root_dir, *extra_it.data, suffix); AppendCppFileLineByLine(&builder, extra_path); } DN_Date date = DN_OS_DateLocalTimeNow(); DN_Str8BuilderPrependF(&builder, "// Generated by the DN single header generator %04u-%02u-%02u %02u:%02u:%02u\n\n", date.year, date.month, date.day, date.hour, date.minutes, date.seconds); DN_Str8 buffer = DN_Str8TrimWhitespaceAround(DN_Str8BuilderBuild(&builder, &scratch.arena)); DN_Str8 single_header_path = DN_OS_PathF(&scratch.arena, "%S/dn_single_header.%S", output_dir, suffix); DN_ErrSink *err = DN_TCErrSinkBeginDefault(); DN_OS_FileWriteAllSafe(single_header_path, buffer, err); DN_ErrSinkEndExitIfErrorF(err, -1, "Failed to write Single header file '%S'", single_header_path); } }