Ver código fonte

REXM: ADDED: Examples validation

Ray 1 mês atrás
pai
commit
68a1190bf2
1 arquivos alterados com 317 adições e 90 exclusões
  1. 317 90
      tools/rexm/rexm.c

+ 317 - 90
tools/rexm/rexm.c

@@ -71,15 +71,36 @@
 //----------------------------------------------------------------------------------
 // raylib example info struct
 typedef struct {
-    char category[16];
-    char name[128];
-    char stars;
-    float verCreated;
-    float verUpdated;
-    char author[64];
-    char authorGitHub[64];
+    char category[16];      // Example category: core, shapes, textures, text, models, shaders, audio, others
+    char name[128];         // Example name: <category>_name_part
+    int stars;              // Example stars count: ★☆☆☆
+    float verCreated;       // Example raylib creation version
+    float verUpdated;       // Example raylib last update version
+    char author[64];        // Example author
+    char authorGitHub[64];  // Example author, GitHub user name
+    int resCount;           // Example resources counter
+    int status;             // Example validation status info
 } rlExampleInfo;
 
+// Validation status for a single example
+typedef enum {
+    VALID_OK                    = 0,        // All required files and entries are present
+    VALID_MISSING_C             = 1 << 0,   // Missing .c source file
+    VALID_MISSING_PNG           = 1 << 1,   // Missing screenshot .png
+    VALID_INVALID_PNG           = 1 << 2,   // Invalid screenshot .png (using template one)
+    VALID_MISSING_RESOURCES     = 1 << 3,   // Missing resources listed in the code
+    VALID_MISSING_VCXPROJ       = 1 << 4,   // Missing Visual Studio .vcxproj file
+    VALID_NOT_IN_VCXSOL         = 1 << 5,   // Project not included in solution file
+    VALID_NOT_IN_MAKEFILE       = 1 << 6,   // Not listed in Makefile
+    VALID_NOT_IN_MAKEFILE_WEB   = 1 << 7,   // Not listed in Makefile.Web
+    VALID_NOT_IN_README         = 1 << 8,   // Not listed in README.md
+    VALID_NOT_IN_JS             = 1 << 9,   // Not listed in examples.js
+    VALID_INCONSISTENT_INFO     = 1 << 10,  // Inconsistent info between collection and example header (stars, author...)
+    VALID_MISSING_WEB_OUTPUT    = 1 << 11,  // Missing .html/.data/.wasm/.js
+    VALID_INVALID_CATEGORY      = 1 << 12,  // Not a recognized category
+    VALID_UNKNOWN_ERROR         = 1 << 13   // Unknown failure case (fallback)
+} rlExampleValidationStatus;
+
 // Example management operations
 typedef enum {
     OP_NONE     = 0,        // No process to do
@@ -107,11 +128,12 @@ static const char *exCollectionFilePath = "C:/GitHub/raylib/examples/examples_li
 //----------------------------------------------------------------------------------
 // Module specific functions declaration
 //----------------------------------------------------------------------------------
+static int FileTextFind(const char *fileName, const char *find);
 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);
-static int FileRemove(const char *fileName);
 static int FileMove(const char *srcPath, const char *dstPath);
+static int FileRemove(const char *fileName);
 
 // Update required files from examples collection
 // UPDATES: Makefile, Makefile.Web, README.md, examples.js
@@ -128,6 +150,9 @@ static void UnloadExamplesData(rlExampleInfo *exInfo);
 static char **LoadTextLines(const char *text, int *count);
 static void UnloadTextLines(char **text);
 
+// Get example info from file header
+static rlExampleInfo *GetExampleInfo(const char *exFileName);
+
 // 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);
@@ -406,76 +431,36 @@ int main(int argc, char *argv[])
 
                 // Get required example info from example file header (if provided)
                 // NOTE: If no example info is provided (other than category/name), just using some default values
-                char *exText = LoadFileText(TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName));
-
-                rlExampleInfo exInfo = { 0 };
-                strcpy(exInfo.category, exCategory);
-                strcpy(exInfo.name, exName);
+                rlExampleInfo *exInfo = GetExampleInfo(TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName));
                 
                 // Get example difficulty stars
                 char starsText[16] = { 0 };
-                int starsIndex = TextFindIndex(exText, "★");
-                if (starsIndex > 0) strncpy(starsText, exText + starsIndex, 3*4); // NOTE: Every UTF-8 star are 3 bytes
-                else strcpy(starsText, "★☆☆☆");
-
-                // Get example create with raylib version
-                char verCreateText[4] = { 0 };
-                int verCreateIndex = TextFindIndex(exText, "created with raylib "); // Version = index + 20
-                if (verCreateIndex > 0) strncpy(verCreateText, exText + verCreateIndex + 20, 3);
-                else strncpy(verCreateText, RAYLIB_VERSION, 3); // Only pick MAJOR.MINOR
-
-                // Get example update with raylib version
-                char verUpdateText[4] = { 0 };
-                int verUpdateIndex = TextFindIndex(exText, "updated with raylib "); // Version = index + 20
-                if (verUpdateIndex > 0) strncpy(verUpdateText, exText + verUpdateIndex + 20, 3);
-                else strncpy(verUpdateText, RAYLIB_VERSION, 3); // Only pick MAJOR.MINOR
-
-                // Get example creator and github user
-                int authorIndex = TextFindIndex(exText, "Example contributed by "); // Author = index + 23
-                int authorGitIndex = TextFindIndex(exText, "(@"); // Author GitHub user = index + 2
-                if (authorIndex > 0)
-                {
-                    int authorNameLen = 0;
-                    if (authorGitIndex > 0) authorNameLen = (authorGitIndex - 1) - (authorIndex + 23);
-                    else
-                    {
-                        int authorNameEndIndex = TextFindIndex(exText + authorIndex, " and reviewed by Ramon Santamaria");
-                        if (authorNameEndIndex == -1) authorNameEndIndex = TextFindIndex(exText + authorIndex, "\n");
-
-                        authorNameLen = authorNameEndIndex - (authorIndex + 23);
-                    }
-                    strncpy(exInfo.author, exText + authorIndex + 23, authorNameLen);
-                }
-                else strcpy(exInfo.author, "<author_name>");
-                if (authorGitIndex > 0)
+                for (int i = 0; i < 4; i++)
                 {
-                    int authorGitEndIndex = TextFindIndex(exText + authorGitIndex, ")");
-                    if (authorGitEndIndex > 0) strncpy(exInfo.authorGitHub, exText + authorGitIndex + 2, authorGitEndIndex - (authorGitIndex + 2));
+                    // NOTE: Every UTF-8 star are 3 bytes
+                    if (i < exInfo->stars) strncpy(starsText + 3*i, "★", 3);
+                    else strncpy(starsText + 3*i, "☆", 3);
                 }
-                else strcpy(exInfo.author, "<user_github>");
-              
-                // TODO: Verify copyright line
-                // Copyright (c) <year_created>-<year_updated> <user_name> (@<user_github>)
 
-                UnloadFileText(exText);
-                
                 if (nextCategoryIndex == -1)
                 {
                     // Add example to collection at the EOF
                     int endIndex = (int)strlen(exCollectionList);
                     memcpy(exCollectionListUpdated, exCollectionList, endIndex);
-                    sprintf(exCollectionListUpdated + endIndex, TextFormat("%s;%s;%s;%s;%s;\"%s\";@%s\n", 
-                        exInfo.category, exInfo.name, starsText, verCreateText, verUpdateText, exInfo.author, exInfo.authorGitHub));
+                    sprintf(exCollectionListUpdated + endIndex, TextFormat("%s;%s;%s;%.2f;%.2f;\"%s\";@%s\n", 
+                        exInfo->category, exInfo->name, starsText, exInfo->verCreated, exInfo->verUpdated, exInfo->author, exInfo->authorGitHub));
                 }
                 else
                 {
                     // Add example to collection, at the end of the category list
                     int categoryIndex = TextFindIndex(exCollectionList, exCategories[nextCategoryIndex]);
                     memcpy(exCollectionListUpdated, exCollectionList, categoryIndex);
-                    int textWritenSize = sprintf(exCollectionListUpdated + categoryIndex, TextFormat("%s;%s;%s;%s;%s;\"%s\";@%s\n",
-                        exInfo.category, exInfo.name, starsText, verCreateText, verUpdateText, exInfo.author, exInfo.authorGitHub));
+                    int textWritenSize = sprintf(exCollectionListUpdated + categoryIndex, TextFormat("%s;%s;%s;%.2f;%.2f;\"%s\";@%s\n",
+                        exInfo->category, exInfo->name, starsText, exInfo->verCreated, exInfo->verUpdated, exInfo->author, exInfo->authorGitHub));
                     memcpy(exCollectionListUpdated + categoryIndex + textWritenSize, exCollectionList + categoryIndex, strlen(exCollectionList) - categoryIndex);
                 }
+
+                RL_FREE(exInfo);
                 
                 SaveFileText(exCollectionFilePath, exCollectionListUpdated);
                 RL_FREE(exCollectionListUpdated);
@@ -703,29 +688,178 @@ int main(int argc, char *argv[])
         case OP_VALIDATE:     // Validate: report and actions
         case OP_UPDATE:
         {
-            // TODO: Validate examples in collection list [examples_list.txt] -> Source of truth!
-            // Validate: raylib/examples/<category>/<category>_example_name.c        -> File exists?
-            // Validate: raylib/examples/<category>/<category>_example_name.png      -> File exists?
-            // Validate: raylib/examples/<category>/resources/..                     -> Example resources available?
-            // Validate: raylib/examples/Makefile                                    -> Example listed?
-            // Validate: raylib/examples/Makefile.Web                                -> Example listed?
-            // Validate: raylib/examples/README.md                                   -> Example listed?
-            // Validate: raylib/projects/VS2022/examples/<category>_example_name.vcxproj -> File exists?
-            // Validate: raylib/projects/VS2022/raylib.sln                           -> Example listed?
-            // Validate: raylib.com/common/examples.js                               -> Example listed?
-            // Validate: raylib.com/examples/<category>/<category>_example_name.html -> File exists?
-            // Validate: raylib.com/examples/<category>/<category>_example_name.data -> File exists?
-            // Validate: raylib.com/examples/<category>/<category>_example_name.wasm -> File exists?
-            // Validate: raylib.com/examples/<category>/<category>_example_name.js   -> File exists?
-
-            // Additional validation elements
-            // Validate: Example naming conventions: <category>/<category>_example_name
-            // Validate: Duplicate entries in collection list
-            // Validate: Example info (stars, author, github) missmatches with example content
-
-            // After validation, update required files for consistency
-            // Update files: Makefile, Makefile.Web, README.md, examples.js
-            UpdateRequiredFiles();
+            /*
+            // Validation flags available:
+            VALID_MISSING_C
+            VALID_MISSING_PNG
+            VALID_INVALID_PNG
+            VALID_MISSING_RESOURCES
+            VALID_MISSING_VCXPROJ
+            VALID_NOT_IN_VCXSOL
+            VALID_NOT_IN_MAKEFILE
+            VALID_NOT_IN_MAKEFILE_WEB
+            VALID_NOT_IN_README
+            VALID_NOT_IN_JS
+            VALID_INCONSISTENT_INFO
+            VALID_MISSING_WEB_OUTPUT
+            VALID_INVALID_CATEGORY
+            */
+
+            // Check all examples in collection [examples_list.txt] -> Source of truth!
+            int exCollectionCount = 0;
+            rlExampleInfo *exCollection = LoadExamplesData(exCollectionFilePath, "ALL", true, &exCollectionCount);
+
+            // TODO: Validate: Duplicate entries in collection list?
+
+            // Get status information for all examples, using "status" field in the struct
+            for (int i = 0; i < exCollectionCount; i++)
+            {
+                rlExampleInfo *exInfo = &exCollection[i];
+                exInfo->status = 0;
+
+                // Validate: raylib/examples/<category>/<category>_example_name.c       -> File exists?
+                if (!FileExists(TextFormat("%s/%s/%s.c", exBasePath, exInfo->category, exInfo->name))) exInfo->status |= VALID_MISSING_C;
+
+                // Validate: raylib/examples/<category>/<category>_example_name.png     -> File exists?
+                if (!FileExists(TextFormat("%s/%s/%s.png", exBasePath, exInfo->category, exInfo->name))) exInfo->status |= VALID_MISSING_PNG;
+                
+                // Validate: example screenshot is not the template default one
+                Image imScreenshot = LoadImage(TextFormat("%s/%s/%s.png", exBasePath, exInfo->category, exInfo->name));
+                Image imTemplate = LoadImage(TextFormat("%s/examples_template.png", exBasePath));
+                if (memcmp(imScreenshot.data, imTemplate.data, GetPixelDataSize(imScreenshot.width, imScreenshot.height, imScreenshot.format)) != 0) exInfo->status |= VALID_INVALID_PNG;
+                UnloadImage(imTemplate);
+                UnloadImage(imScreenshot);
+
+                // Validate: raylib/examples/Makefile                                   -> Example listed?
+                if (FileTextFind(TextFormat("%s/Makefile", exBasePath), exInfo->name) == -1) exInfo->status |= VALID_NOT_IN_MAKEFILE;
+
+                // Validate: raylib/examples/Makefile.Web                               -> Example listed?
+                if (FileTextFind(TextFormat("%s/Makefile.Web", exBasePath), exInfo->name) == -1) exInfo->status |= VALID_NOT_IN_MAKEFILE_WEB;
+
+                // Validate: raylib/examples/README.md                                  -> Example listed?
+                if (FileTextFind(TextFormat("%s/README.md", exBasePath), exInfo->name) == -1) exInfo->status |= VALID_NOT_IN_JS;
+                
+                // Validate: raylib.com/common/examples.js                              -> Example listed?
+                if (FileTextFind(TextFormat("%s/common/examples.js", exWebPath), exInfo->name) == -1) exInfo->status |= VALID_NOT_IN_README;
+
+                // Validate: raylib/projects/VS2022/examples/<category>_example_name.vcxproj -> File exists?
+                if (!FileExists(TextFormat("%s/../projects/VS2022/examples/%s.png", exBasePath, exInfo->name))) exInfo->status |= VALID_MISSING_VCXPROJ;
+
+                // Validate: raylib/projects/VS2022/raylib.sln                          -> Example listed?
+                if (FileTextFind(TextFormat("%s/../projects/VS2022/raylib.sln", exBasePath), exInfo->name) == -1) exInfo->status |= VALID_NOT_IN_VCXSOL;
+
+                // Validate: raylib/examples/<category>/resources/..                    -> Example resources available?
+                // Scan resources used in example to check for missing resource files
+                char **resPaths = ScanExampleResources(TextFormat("%s/%s/%s.c", exBasePath, exInfo->category, exInfo->name), &exInfo->resCount);
+                if (exInfo->resCount > 0)
+                {
+                    for (int r = 0; r < exInfo->resCount; r++)
+                    {
+                        // WARNING: Special case to consider: shaders, resource paths could use conditions: "glsl%i"
+                        // In this case, multiple resources are required: glsl100, glsl120, glsl330
+                        if (TextFindIndex(resPaths[r], "glsl%i") > -1)
+                        {
+                            int glslVer[3] = { 100, 120, 330 };
+
+                            for (int v = 0; v < 3; v++)
+                            {
+                                char *resPathUpdated = TextReplace(resPaths[r], "glsl%i", TextFormat("glsl%i", glslVer[v]));
+                                if (!FileExists(TextFormat("%s/%s/%s", exBasePath, exInfo->category, resPathUpdated))) exInfo->status |= VALID_MISSING_RESOURCES;
+                                RL_FREE(resPathUpdated);
+                            }
+                        }
+                        else
+                        {
+                            if (!FileExists(TextFormat("%s/%s/%s", exBasePath, exInfo->category, resPaths[r]))) exInfo->status |= VALID_MISSING_RESOURCES;
+                        }
+                    }
+                }
+                ClearExampleResources(resPaths);
+
+                // Validate: raylib.com/examples/<category>/<category>_example_name.html -> File exists?
+                // Validate: raylib.com/examples/<category>/<category>_example_name.data -> File exists?
+                // Validate: raylib.com/examples/<category>/<category>_example_name.wasm -> File exists?
+                // Validate: raylib.com/examples/<category>/<category>_example_name.js   -> File exists?
+                if ((!FileExists(TextFormat("%s/examples/%s/%s.html", exWebPath, exInfo->category, exInfo->name))) ||
+                    ((exInfo->resCount > 0) && !FileExists(TextFormat("%s/examples/%s/%s.data", exWebPath, exInfo->category, exInfo->name))) ||
+                    (!FileExists(TextFormat("%s/examples/%s/%s.wasm", exWebPath, exInfo->category, exInfo->name))) ||
+                    (!FileExists(TextFormat("%s/examples/%s/%s.js", exWebPath, exInfo->category, exInfo->name)))) exInfo->status |= VALID_MISSING_WEB_OUTPUT;
+
+                // NOTE: Additional validation elements
+                // Validate: Example naming conventions: <category>/<category>_example_name, valid category
+                if ((TextFindIndex(exInfo->name, exInfo->category) == -1) || 
+                    (!TextIsEqual(exInfo->category, "core") || !TextIsEqual(exInfo->category, "shapes") || 
+                     !TextIsEqual(exInfo->category, "textures") || !TextIsEqual(exInfo->category, "text") || 
+                     !TextIsEqual(exInfo->category, "models") || !TextIsEqual(exInfo->category, "shaders") || 
+                     !TextIsEqual(exInfo->category, "audio") || !TextIsEqual(exInfo->category, "others"))) exInfo->status |= VALID_INVALID_CATEGORY;
+
+                // Validate: Example info (stars, author, github) missmatches with example header content
+                rlExampleInfo *exInfoHeader = GetExampleInfo(TextFormat("%s/%s/%s.c", exBasePath, exInfo->category, exInfo->name));
+
+                if ((strcmp(exInfo->name, exInfoHeader->name) != 0) ||     // NOTE: Get it from example, not file
+                    (strcmp(exInfo->category, exInfoHeader->category) != 0) ||
+                    (strcmp(exInfo->author, exInfoHeader->author) != 0) ||
+                    (strcmp(exInfo->authorGitHub, exInfoHeader->authorGitHub) != 0) ||
+                    (exInfo->stars != exInfoHeader->stars) ||
+                    (exInfo->verCreated != exInfoHeader->verCreated) ||
+                    (exInfo->verUpdated != exInfoHeader->verUpdated)) exInfo->status |= VALID_INCONSISTENT_INFO;
+
+                RL_FREE(exInfoHeader);
+
+                // TODO: Generate validation report/table with results (.md)
+                /*
+                //Status	    Description
+                //OK	        Everything found
+                //MISSING	    One or more files are missing, some can be solved automatically
+                //ERROR	        Serious inconsistencies
+
+                Columns:
+                [C]     VALID_MISSING_C             // Missing .c source file
+                [PNG]   VALID_MISSING_PNG           // Missing screenshot .png
+                [WPNG]  VALID_INVALID_PNG           // Invalid png screenshot (using template one)
+                [RES]   VALID_MISSING_RESOURCES     // Missing resources listed in the code
+                [VCX]   VALID_MISSING_VCXPROJ       // Missing Visual Studio .vcxproj file
+                [SOL]   VALID_NOT_IN_VCXSOL         // Project not included in solution file
+                [MK]    VALID_NOT_IN_MAKEFILE       // Not listed in Makefile
+                [MKWEB] VALID_NOT_IN_MAKEFILE_WEB   // Not listed in Makefile.Web
+                [RDME]  VALID_NOT_IN_README         // Not listed in README.md
+                [JS]    VALID_NOT_IN_JS             // Not listed in examples.js
+                [WOUT]  VALID_MISSING_WEB_OUTPUT    // Missing .html/.data/.wasm/.js
+                [INFO]  VALID_INCONSISTENT_INFO     // Inconsistent info between collection and example header (stars, author...)
+                [CAT]   VALID_INVALID_CATEGORY      // Not a recognized category
+
+                [STATUS]  [CATEGORY]   [NAME]          [C] [PNG] [WPNG] [RES] [VCX] [SOL] [MK] [MKWEB] [RDME] [JS] [WOUT] [INFO] [CAT]
+                -----------------------------------------------------------------------------------------------------------------------
+                OK          core      basic_window      ✔   ✔     ✔     ✔    ✔    ✔    ✔     ✔      ✔    ✔    ✔      ✔    ✔
+                MISSING     shapes    colorful_lines    ✘   ✔     ✘     ✔    ✘    ✔    ✔     ✘      ✔    ✔    ✔      ✔    ✔
+                ERROR       text      broken_shader     ✘   ✘     ✘     ✘    ✘    ✘    ✘     ✘      ✔    ✘    ✔      ✔    ✔
+                */
+            }
+
+            UnloadExamplesData(exCollection);
+            //------------------------------------------------------------------------------------------------
+
+            if (opCode == OP_UPDATE)
+            {
+                // Actions to be performed to fix/review anything possible
+                //------------------------------------------------------------------------------------------------
+                
+                // TODO: Process validation results and take actions to 
+                // correct everything possible to make collection consistent
+
+                // Review: Add/Remove: raylib/projects/VS2022/examples/<category>_example_name.vcxproj
+                // Review: Add/remove: raylib/projects/VS2022/raylib.sln
+
+                // Update files: Makefile, Makefile.Web, README.md, examples.js
+                UpdateRequiredFiles();
+
+                // Review examples
+                // Review: Add/Remove: raylib.com/examples/<category>/<category>_example_name.html
+                // Review: Add/Remove: raylib.com/examples/<category>/<category>_example_name.data
+                // Review: Add/Remove: raylib.com/examples/<category>/<category>_example_name.wasm
+                // Review: Add/Remove: raylib.com/examples/<category>/<category>_example_name.js
+                //------------------------------------------------------------------------------------------------
+            }
             
         } break;
         default:    // Help
@@ -1156,6 +1290,21 @@ static void UnloadExamplesData(rlExampleInfo *exInfo)
     RL_FREE(exInfo);
 }
 
+// Find text in existing file
+static int FileTextFind(const char *fileName, const char *find)
+{
+    int result = -1;
+
+    if (FileExists(fileName))
+    {
+        char *fileText = LoadFileText(fileName);
+        result = TextFindIndex(fileText, find);
+        UnloadFileText(fileText);
+    }
+
+    return result; 
+}
+
 // Replace text in an existing file
 static int FileTextReplace(const char *fileName, const char *textLookUp, const char *textReplace)
 {
@@ -1264,6 +1413,84 @@ static void UnloadTextLines(char **lines)
     RL_FREE(lines);
 }
 
+// Get example info from file header
+rlExampleInfo *GetExampleInfo(const char *exFileName)
+{
+    rlExampleInfo *exInfo = (rlExampleInfo *)RL_CALLOC(1, sizeof(rlExampleInfo));
+    
+    if (IsFileExtension(exFileName, ".c"))
+    {
+        strcpy(exInfo->name, GetFileNameWithoutExt(exFileName));
+        strncpy(exInfo->category, exInfo->name, TextFindIndex(exInfo->name, "_"));
+
+        char *exText = LoadFileText(exFileName);
+
+        // Get example difficulty stars
+        // NOTE: Counting the unicode char occurrences: ⭐️
+        int starsIndex = TextFindIndex(exText, "★");
+        if (starsIndex > 0)
+        {
+            const char *starPtr = exText + starsIndex;
+            while (*starPtr)
+            {
+                if (((unsigned char)starPtr[0] == 0xe2) &&
+                    ((unsigned char)starPtr[1] == 0xad) &&
+                    ((unsigned char)starPtr[2] == 0x90))
+                {
+                    exInfo->stars++;
+                    starPtr += 3; // Advance past multibyte character
+                }
+                else starPtr++;
+            }
+        }
+
+        // Get example create with raylib version
+        char verCreateText[4] = { 0 };
+        int verCreateIndex = TextFindIndex(exText, "created with raylib "); // Version = index + 20
+        if (verCreateIndex > 0) strncpy(verCreateText, exText + verCreateIndex + 20, 3);
+        else strncpy(verCreateText, RAYLIB_VERSION, 3); // Only pick MAJOR.MINOR
+        exInfo->verCreated = TextToFloat(verCreateText);
+
+        // Get example update with raylib version
+        char verUpdateText[4] = { 0 };
+        int verUpdateIndex = TextFindIndex(exText, "updated with raylib "); // Version = index + 20
+        if (verUpdateIndex > 0) strncpy(verUpdateText, exText + verUpdateIndex + 20, 3);
+        else strncpy(verUpdateText, RAYLIB_VERSION, 3); // Only pick MAJOR.MINOR
+        exInfo->verUpdated = TextToFloat(verUpdateText);
+
+        // Get example creator and github user
+        int authorIndex = TextFindIndex(exText, "Example contributed by "); // Author = index + 23
+        int authorGitIndex = TextFindIndex(exText, "(@"); // Author GitHub user = index + 2
+        if (authorIndex > 0)
+        {
+            int authorNameLen = 0;
+            if (authorGitIndex > 0) authorNameLen = (authorGitIndex - 1) - (authorIndex + 23);
+            else
+            {
+                int authorNameEndIndex = TextFindIndex(exText + authorIndex, " and reviewed by Ramon Santamaria");
+                if (authorNameEndIndex == -1) authorNameEndIndex = TextFindIndex(exText + authorIndex, "\n");
+
+                authorNameLen = authorNameEndIndex - (authorIndex + 23);
+            }
+            strncpy(exInfo->author, exText + authorIndex + 23, authorNameLen);
+        }
+        else strcpy(exInfo->author, "<author_name>");
+        if (authorGitIndex > 0)
+        {
+            int authorGitEndIndex = TextFindIndex(exText + authorGitIndex, ")");
+            if (authorGitEndIndex > 0) strncpy(exInfo->authorGitHub, exText + authorGitIndex + 2, authorGitEndIndex - (authorGitIndex + 2));
+        }
+        else strcpy(exInfo->authorGitHub, "<user_github>");
+
+        // TODO: Verify copyright line
+        // Copyright (c) <year_created>-<year_updated> <user_name> (@<user_github>)
+
+        UnloadFileText(exText);
+    }
+
+    return exInfo;
+}
+
 // 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)
@@ -1283,17 +1510,17 @@ static int ParseExampleInfoLine(const char *line, rlExampleInfo *entry)
 
     // Parsing stars
     // NOTE: Counting the unicode char occurrences: ⭐️
-    const char *ptr = tokens[2];
-    while (*ptr) 
+    const char *starPtr = tokens[2];
+    while (*starPtr) 
     {
-        if (((unsigned char)ptr[0] == 0xE2) && 
-            ((unsigned char)ptr[1] == 0xAD) && 
-            ((unsigned char)ptr[2] == 0x90))
+        if (((unsigned char)starPtr[0] == 0xe2) && 
+            ((unsigned char)starPtr[1] == 0xad) && 
+            ((unsigned char)starPtr[2] == 0x90))
         {
             entry->stars++;
-            ptr += 3; // Advance past multibyte character
+            starPtr += 3; // Advance past multibyte character
         }
-        else ptr++;
+        else starPtr++;
     }
 
     // Get raylib creation/update versions
@@ -1352,7 +1579,7 @@ static char **ScanExampleResources(const char *filePath, int *resPathCount)
             char *end = strchr(start, '"');
             if (!end) break;
 
-            int len = end - start;
+            int len = (int)(end - start);
             if ((len > 0) && (len < REXM_MAX_RESOURCE_PATH_LEN))
             {
                 char buffer[REXM_MAX_RESOURCE_PATH_LEN] = { 0 };