From 947783819381ccb48caeb477b7d21e013bba16ea Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 1 Aug 2025 19:40:44 +0200 Subject: [PATCH] ADDED: Some useful functions for examples info loading, using `examples_list` --- examples/rexm.c | 294 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 240 insertions(+), 54 deletions(-) diff --git a/examples/rexm.c b/examples/rexm.c index 8440188c9..6a1c27826 100644 --- a/examples/rexm.c +++ b/examples/rexm.c @@ -7,6 +7,7 @@ * - add * - rename * - remove +* - validate * * Files involved in the processes: * - raylib/examples//_example_name.c @@ -60,22 +61,50 @@ //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- +// raylib example info struct +typedef struct { + char category[16]; + char name[64]; + char stars; + float verCreated; + float verUpdated; + char author[64]; + char authorGitHub[32]; +} rlExampleInfo; + // Example management operations typedef enum { - OP_NONE = 0, - OP_CREATE = 1, - OP_ADD = 2, - OP_RENAME = 3, - OP_REMOVE = 4 -} ExampleOperation; + OP_NONE = 0, // No process to do + OP_CREATE = 1, // Create new example, using default template + OP_ADD = 2, // Add existing examples (hopefully following template) + OP_RENAME = 3, // Rename existing example + OP_REMOVE = 4, // Remove existing example + OP_VALIDATE = 5, // Validate examples, using [examples_list.txt] as main source by default +} rlExampleOperation; //---------------------------------------------------------------------------------- // Module specific functions declaration //---------------------------------------------------------------------------------- static int FileTextReplace(const char *fileName, const char *textLookUp, const char *textReplace); static int FileCopy(const char *srcPath, const char *dstPath); -static int FileRename(const char *fileName, const char *fileRename); // TODO: Implement, make sure to deal with paths moving -static int FileRemove(const char *fileName); // TODO: Implement +static int FileRename(const char *fileName, const char *fileRename); +static int FileRemove(const char *fileName); + +// Load examples collection information +static rlExampleInfo *LoadExamplesData(const char *fileName, int *exCount); +static void UnloadExamplesData(rlExampleInfo *exInfo); + +// Get text lines (by line-breaks '\n') +// WARNING: It does not copy text data, just returns line pointers +static const char **GetTextLines(const char *text, int *count); + +// raylib example line info parser +// Parses following line format: core/core_basic_window;⭐️☆☆☆;1.0;1.0;"Ray"/@raysan5 +static int ParseExampleInfoLine(const char *line, rlExampleInfo *entry); + +// Sort array of strings by name +// WARNING: items[] pointers are reorganized +static void SortStringsByName(char **items, int count); //------------------------------------------------------------------------------------ // Program main entry point @@ -87,15 +116,14 @@ int main(int argc, char *argv[]) char *exBasePath = "C:/GitHub/raylib/examples"; char *exWebPath = "C:/GitHub/raylib.com/examples"; char *exTemplateFilePath = "C:/GitHub/raylib/examples/examples_template.c"; - + char *exCollectionList = "C:/GitHub/raylib/examples/examples_list.txt"; + char inFileName[1024] = { 0 }; // Example input filename - char exName[1024] = { 0 }; // Example name, without extension: core_basic_window + char exName[64] = { 0 }; // Example name, without extension: core_basic_window char exCategory[32] = { 0 }; // Example category: core - char exRename[1024] = { 0 }; // Example re-name, without extension - char exPath[1024] = { 0 }; // Example path -NOT USED- - char exFullPath[1024] = { 0 }; // Example full path -NOT USED- - + char exRename[64] = { 0 }; // Example re-name, without extension + int opCode = OP_NONE; // Operation code: 0-None(Help), 1-Create, 2-Add, 3-Rename, 4-Remove // Command-line usage mode @@ -108,6 +136,7 @@ int main(int argc, char *argv[]) // add : Add existing example, category extracted from name // rename : Rename an existing example // remove : Remove an existing example + // validate : Validate examples collection if (strcmp(argv[1], "create") == 0) { // Check for valid upcoming argument @@ -116,7 +145,7 @@ int main(int argc, char *argv[]) else { // TODO: Additional security checks for file name? - + strcpy(inFileName, argv[2]); // Register filename for creation opCode = 1; } @@ -131,7 +160,7 @@ int main(int argc, char *argv[]) if (IsFileExtension(argv[2], ".c")) // Check for valid file extension: input { // TODO: Parse category name from filename provided! - + strcpy(inFileName, argv[2]); // Register filename for creation opCode = 2; } @@ -141,17 +170,11 @@ int main(int argc, char *argv[]) else if (strcmp(argv[1], "rename") == 0) { if (argc == 2) LOG("WARNING: No filename provided to create\n"); - //else if (argc == 3) LOG("WARNING: No enough arguments provided\n"); All the documentation says 3 args but I don't mind being wrong - else if (argc > 3) LOG("WARNING: Too many arguments provided\n"); + else if (argc == 3) LOG("WARNING: No enough arguments provided\n"); + else if (argc > 4) LOG("WARNING: Too many arguments provided\n"); else { - strcpy(exName, argv[2]); - for (int index = 0; index < 32; index++) - { - if (exName[index] == '_') break; - exCategory[index] = exName[index]; - } - strcpy(exRename, argv[3]); + // TODO: Register exName, exCategory and exRename opCode = 3; } @@ -167,8 +190,12 @@ int main(int argc, char *argv[]) opCode = 4; } } + else if (strcmp(argv[1], "validate") == 0) + { + opCode = 5; + } } - + switch (opCode) { case 1: // Create: New example from template @@ -181,33 +208,33 @@ int main(int argc, char *argv[]) if ((opCode != 1) && FileExists(inFileName)) { FileCopy(inFileName, TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName)); - } - + } + // Generate all required files //-------------------------------------------------------------------------------- // Create: raylib/examples//_example_name.c // Create: raylib/examples//_example_name.png - FileCopy("C:/GitHub/raylib/examples/examples_template.png", + FileCopy("C:/GitHub/raylib/examples/examples_template.png", TextFormat("%s/%s/%s.png", exBasePath, exCategory, exName)); // To be updated manually! - + // Copy: raylib/examples//resources/*.* ---> To be updated manually! - + // TODO: Update the required files to add new example in the required position (ordered by category and name), // it could require some logic to make it possible... - + // Edit: raylib/examples/Makefile --> Add new example // Edit: raylib/examples/Makefile.Web --> Add new example // Edit: raylib/examples/README.md --> Add new example - + // Create: raylib/projects/VS2022/examples/_example_name.vcxproj // Edit: raylib/projects/VS2022/raylib.sln --> Add new example // Edit: raylib.com/common/examples.js --> Add new example - + // Compile to: raylib.com/examples//_example_name.html // Compile to: raylib.com/examples//_example_name.data // Compile to: raylib.com/examples//_example_name.wasm // Compile to: raylib.com/examples//_example_name.js - + // Recompile example (on raylib side) // NOTE: Tools requirements: emscripten, w64devkit system(TextFormat("%s/../build_example_web.bat %s\%s", exBasePath, exCategory, exName)); @@ -225,19 +252,19 @@ int main(int argc, char *argv[]) case 3: // Rename { // Rename all required files - rename(TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName), + rename(TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName), TextFormat("%s/%s/%s.c", exBasePath, exCategory, exRename)); - rename(TextFormat("%s/%s/%s.png", exBasePath, exCategory, exName), + rename(TextFormat("%s/%s/%s.png", exBasePath, exCategory, exName), TextFormat("%s/%s/%s.png", exBasePath, exCategory, exRename)); - + FileTextReplace(TextFormat("%s/Makefile", exBasePath), exName, exRename); FileTextReplace(TextFormat("%s/Makefile.Web", exBasePath), exName, exRename); FileTextReplace(TextFormat("%s/README.md", exBasePath), exName, exRename); - - rename(TextFormat("%s/../projects/VS2022/examples/%s.vcxproj", exBasePath, exName), + + rename(TextFormat("%s/../projects/VS2022/examples/%s.vcxproj", exBasePath, exName), TextFormat("%s/../projects/VS2022/examples/%s.vcxproj", exBasePath, exRename)); FileTextReplace(TextFormat("%s/../projects/VS2022/raylib.sln", exBasePath), exName, exRename); - + // Remove old web compilation FileTextReplace(TextFormat("%s/../common/examples.js", exWebPath), exName, exRename); remove(TextFormat("%s/%s/%s.html", exWebPath, exCategory, exName)); @@ -263,6 +290,25 @@ int main(int argc, char *argv[]) { // TODO: Remove and update all required files... + } break; + case 5: // Validate + { + // TODO: Validate examples collection against [examples_list.txt] + + // Validate: raylib/examples//_example_name.c + // Validate: raylib/examples//_example_name.png + // Validate: raylib/examples//resources/.. -> Not possible for now... + // Validate: raylib/examples/Makefile + // Validate: raylib/examples/Makefile.Web + // Validate: raylib/examples/README.md + // Validate: raylib/projects/VS2022/examples/_example_name.vcxproj + // Validate: raylib/projects/VS2022/raylib.sln + // Validate: raylib.com/common/examples.js + // Validate: raylib.com/examples//_example_name.html + // Validate: raylib.com/examples//_example_name.data + // Validate: raylib.com/examples//_example_name.wasm + // Validate: raylib.com/examples//_example_name.js + } break; default: // Help { @@ -272,7 +318,7 @@ int main(int argc, char *argv[]) // add : Add existing example, category extracted from name // rename : Rename an existing example // remove : Remove an existing example - + printf("\n////////////////////////////////////////////////////////////////////////////////////////////\n"); printf("// //\n"); printf("// rexm [raylib examples manager] - A simple command-line tool to manage raylib examples //\n"); @@ -299,57 +345,197 @@ int main(int argc, char *argv[]) printf(" Renames and updates example to \n\n"); } break; } - + return 0; } //---------------------------------------------------------------------------------- // Module specific functions definition //---------------------------------------------------------------------------------- +// Load examples collection information +static rlExampleInfo *LoadExamplesData(const char *fileName, int *exCount) +{ + #define MAX_EXAMPLES_INFO 256 + + *exCount = 0; + rlExampleInfo *exInfo = (rlExampleInfo *)RL_CALLOC(MAX_EXAMPLES_INFO, sizeof(rlExampleInfo)); + + const char *text = LoadFileText(fileName); + + if (text != NULL) + { + int lineCount = 0; + const char **linePtrs = GetTextLines(text, &lineCount); + + for (int i = 0; i < lineCount; i++) + { + // Basic validation for lines start categories + if ((linePtrs[i][0] != '#') && + ((linePtrs[i][0] == 'c') || // core + (linePtrs[i][0] == 's') || // shapes, shaders + (linePtrs[i][0] == 't') || // textures, text + (linePtrs[i][0] == 'm') || // models + (linePtrs[i][0] == 'a') || // audio + (linePtrs[i][0] == 'o'))) // others + { + if (ParseExampleInfoLine(linePtrs[i], &exInfo[*exCount]) == 0) *exCount += 1; + } + } + } + + return exInfo; +} + +// Unload examples collection data +static void UnloadExamplesData(rlExampleInfo *exInfo) +{ + RL_FREE(exInfo); +} + +// Replace text in an existing file static int FileTextReplace(const char *fileName, const char *textLookUp, const char *textReplace) { int result = 0; char *fileText = NULL; char *fileTextUpdated = { 0 }; - - fileText = LoadFileText(fileName); - fileTextUpdated = TextReplace(fileText, textLookUp, textReplace); - result = SaveFileText(fileName, fileTextUpdated); - MemFree(fileTextUpdated); - UnloadFileText(fileText); + if (FileExists(fileName)) + { + fileText = LoadFileText(fileName); + fileTextUpdated = TextReplace(fileText, textLookUp, textReplace); + result = SaveFileText(fileName, fileTextUpdated); + MemFree(fileTextUpdated); + UnloadFileText(fileText); + } + return result; } +// Copy file from one path to another +// WARNING: Destination path must exist static int FileCopy(const char *srcPath, const char *dstPath) { int result = 0; int srcDataSize = 0; unsigned char *srcFileData = LoadFileData(srcPath, &srcDataSize); + // TODO: Create required paths if they do not exist + if ((srcFileData != NULL) && (srcDataSize > 0)) result = SaveFileData(dstPath, srcFileData, srcDataSize); UnloadFileData(srcFileData); - + return result; } +// Rename file (if exists) +// NOTE: Only rename file name required, not full path static int FileRename(const char *fileName, const char *fileRename) { int result = 0; - - // TODO: Make sure to deal with paths properly for file moving if required - + if (FileExists(fileName)) rename(fileName, TextFormat("%s/%s", GetDirectoryPath(fileName), fileRename)); return result; } +// Remove file (if exists) static int FileRemove(const char *fileName) { int result = 0; - + if (FileExists(fileName)) remove(fileName); return result; } + +// Get text lines (by line-breaks '\n') +// WARNING: It does not copy text data, just returns line pointers +static const char **GetTextLines(const char *text, int *count) +{ + #define MAX_TEXT_LINE_PTRS 128 + + static const char *linePtrs[MAX_TEXT_LINE_PTRS] = { 0 }; + for (int i = 0; i < MAX_TEXT_LINE_PTRS; i++) linePtrs[i] = NULL; // Init NULL pointers to substrings + + int textSize = (int)strlen(text); + + linePtrs[0] = text; + int len = 0; + *count = 1; + + for (int i = 0, k = 0; (i < textSize) && (*count < MAX_TEXT_LINE_PTRS); i++) + { + if (text[i] == '\n') + { + k++; + linePtrs[k] = &text[i + 1]; // WARNING: next value is valid? + len = 0; + *count += 1; + } + else len++; + } + + return linePtrs; +} + +// raylib example line info parser +// Parses following line format: core/core_basic_window;⭐️☆☆☆;1.0;1.0;"Ray"/@raysan5 +static int ParseExampleInfoLine(const char *line, rlExampleInfo *entry) +{ + #define MAX_EXAMPLE_INFO_LINE_LEN 512 + + char temp[MAX_EXAMPLE_INFO_LINE_LEN] = { 0 }; + strncpy(temp, line, MAX_EXAMPLE_INFO_LINE_LEN); // WARNING: Copy is needed because strtok() modifies string, adds '\0' + temp[MAX_EXAMPLE_INFO_LINE_LEN - 1] = '\0'; // Ensure null termination + + int tokenCount = 0; + char **tokens = TextSplit(line, ';', &tokenCount); + + // Get category and name + strncpy(entry->category, tokens[0], sizeof(entry->category)); + strncpy(entry->name, tokens[1], sizeof(entry->name)); + + // Parsing stars + // NOTE: Counting the unicode char occurrences: ⭐️ + const char *ptr = tokens[2]; + while (*ptr) + { + if (((unsigned char)ptr[0] == 0xE2) && + ((unsigned char)ptr[1] == 0xAD) && + ((unsigned char)ptr[2] == 0x90)) + { + entry->stars++; + ptr += 3; // Advance past multibyte character + } + else ptr++; + } + + // Get raylib creation/update versions + entry->verCreated = strtof(tokens[3], NULL); + entry->verUpdated = strtof(tokens[4], NULL); + + // Get author and github + char *quote1 = strchr(tokens[5], '"'); + char *quote2 = quote1? strchr(quote1 + 1, '"') : NULL; + if (quote1 && quote2) strncpy(entry->author, quote1 + 1, sizeof(entry->author)); + strncpy(entry->authorGitHub, tokens[6], sizeof(entry->authorGitHub)); + + return 1; +} + +// Text compare, required for qsort() function +static int SortTextCompare(const void *a, const void *b) +{ + const char *str1 = *(const char **)a; + const char *str2 = *(const char **)b; + + return strcmp(str1, str2); +} + +// Sort array of strings by name +// WARNING: items[] pointers are reorganized +static void SortStringsByName(char **items, int count) +{ + qsort(items, count, sizeof(char *), SortTextCompare); +}