2025-08-01 00:30:01 +02:00
/*******************************************************************************************
*
* rexm [ raylib examples manager ] - A simple command - line tool to manage raylib examples
*
* Supported processes :
* - create < new_example_name >
* - add < example_name >
* - rename < old_examples_name > < new_example_name >
* - remove < example_name >
2025-08-01 19:40:44 +02:00
* - validate
2025-08-05 12:38:11 +02:00
* - update
2025-08-01 00:30:01 +02:00
*
* Files involved in the processes :
* - raylib / examples / < category > / < category > _example_name . c
* - raylib / examples / < category > / < category > _example_name . png
* - raylib / examples / < category > / resources / . .
* - raylib / examples / Makefile
* - raylib / examples / Makefile . Web
* - raylib / examples / README . md
* - raylib / projects / VS2022 / examples / < category > _example_name . vcxproj
* - raylib / projects / VS2022 / raylib . sln
* - raylib . com / common / examples . js
* - raylib . com / examples / < category > / < category > _example_name . html
* - raylib . com / examples / < category > / < category > _example_name . data
* - raylib . com / examples / < category > / < category > _example_name . wasm
* - raylib . com / examples / < category > / < category > _example_name . js
*
* LICENSE : zlib / libpng
*
* Copyright ( c ) 2025 Ramon Santamaria ( @ raysan5 )
*
* This software is provided " as-is " , without any express or implied warranty . In no event
* will the authors be held liable for any damages arising from the use of this software .
*
* Permission is granted to anyone to use this software for any purpose , including commercial
* applications , and to alter it and redistribute it freely , subject to the following restrictions :
*
* 1. The origin of this software must not be misrepresented ; you must not claim that you
* wrote the original software . If you use this software in a product , an acknowledgment
* in the product documentation would be appreciated but is not required .
*
* 2. Altered source versions must be plainly marked as such , and must not be misrepresented
* as being the original software .
*
* 3. This notice may not be removed or altered from any source distribution .
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# include "raylib.h"
# include <stdlib.h>
# include <stdio.h> // Required for: rename(), remove()
# include <string.h> // Required for: strcmp(), strcpy()
# define SUPPORT_LOG_INFO
# if defined(SUPPORT_LOG_INFO) && defined(_DEBUG)
# define LOG(...) printf(__VA_ARGS__)
# else
# define LOG(...)
# endif
2025-08-05 12:37:11 +02:00
# define REXM_MAX_EXAMPLES 512
# define REXM_MAX_EXAMPLE_CATEGORIES 8
2025-08-04 17:51:48 +02:00
2025-08-05 12:37:11 +02:00
# define REXM_MAX_BUFFER_SIZE (2*1024*1024) // 2MB
# define REXM_MAX_RESOURCE_PATHS 256
2025-08-04 17:51:48 +02:00
2025-08-01 00:30:01 +02:00
//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------
2025-08-01 19:40:44 +02:00
// raylib example info struct
typedef struct {
2025-08-06 00:56:29 +02:00
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
2025-08-19 10:38:07 +02:00
2025-08-06 00:56:29 +02:00
int status ; // Example validation status info
2025-08-19 10:38:07 +02:00
int resCount ; // Example resources counter
char * * resPaths ; // Example resources paths (MAX: 256)
2025-08-01 19:40:44 +02:00
} rlExampleInfo ;
2025-08-06 00:56:29 +02:00
// 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 ;
2025-08-01 00:30:01 +02:00
// Example management operations
typedef enum {
2025-08-05 12:38:11 +02:00
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
OP_UPDATE = 6 , // Validate and update required examples (as far as possible)
2025-08-01 19:40:44 +02:00
} rlExampleOperation ;
2025-08-01 00:30:01 +02:00
2025-08-05 12:38:30 +02:00
static const char * exCategories [ REXM_MAX_EXAMPLE_CATEGORIES ] = { " core " , " shapes " , " textures " , " text " , " models " , " shaders " , " audio " , " others " } ;
2025-08-02 18:00:41 +02:00
2025-08-03 22:56:15 +02:00
// Paths required for examples management
2025-08-06 18:14:52 +02:00
// NOTE: Paths can be provided with environment variables
static const char * exBasePath = NULL ; // Env: REXM_EXAMPLES_BASE_PATH
static const char * exWebPath = NULL ; // Env: REXM_EXAMPLES_WEB_PATH
static const char * exTemplateFilePath = NULL ; // Env: REXM_EXAMPLES_TEMPLATE_FILE_PATH
static const char * exTemplateScreenshot = NULL ; // Env: REXM_EXAMPLES_TEMPLATE_SCREENSHOT_PATH
static const char * exCollectionFilePath = NULL ; // Env: REXM_EXAMPLES_COLLECTION_FILE_PATH
2025-08-19 11:00:22 +02:00
static const char * exVSProjectSolutionFile = NULL ; // Env REXM_EXAMPLES_VS2022_SLN_FILE
2025-08-03 22:56:15 +02:00
2025-08-01 00:30:01 +02:00
//----------------------------------------------------------------------------------
// Module specific functions declaration
//----------------------------------------------------------------------------------
2025-08-06 00:56:29 +02:00
static int FileTextFind ( const char * fileName , const char * find ) ;
2025-08-06 18:14:52 +02:00
static int FileTextReplace ( const char * fileName , const char * find , const char * replace ) ;
2025-08-01 00:30:01 +02:00
static int FileCopy ( const char * srcPath , const char * dstPath ) ;
2025-08-01 19:40:44 +02:00
static int FileRename ( const char * fileName , const char * fileRename ) ;
2025-08-04 19:27:48 +02:00
static int FileMove ( const char * srcPath , const char * dstPath ) ;
2025-08-06 00:56:29 +02:00
static int FileRemove ( const char * fileName ) ;
2025-08-01 19:40:44 +02:00
2025-08-03 22:56:15 +02:00
// Update required files from examples collection
// UPDATES: Makefile, Makefile.Web, README.md, examples.js
static int UpdateRequiredFiles ( void ) ;
2025-08-01 19:40:44 +02:00
// Load examples collection information
2025-08-02 18:00:41 +02:00
// NOTE 1: Load by category: "ALL", "core", "shapes", "textures", "text", "models", "shaders", others"
// NOTE 2: Sort examples list on request flag
static rlExampleInfo * LoadExamplesData ( const char * fileName , const char * category , bool sort , int * exCount ) ;
2025-08-01 19:40:44 +02:00
static void UnloadExamplesData ( rlExampleInfo * exInfo ) ;
// Get text lines (by line-breaks '\n')
// WARNING: It does not copy text data, just returns line pointers
2025-08-03 01:17:50 +02:00
static char * * LoadTextLines ( const char * text , int * count ) ;
static void UnloadTextLines ( char * * text ) ;
2025-08-01 19:40:44 +02:00
2025-08-06 14:04:30 +02:00
// Load example info from file header
static rlExampleInfo * LoadExampleInfo ( const char * exFileName ) ;
static void UnloadExampleInfo ( rlExampleInfo * exInfo ) ;
2025-08-06 00:56:29 +02:00
2025-08-01 19:40:44 +02:00
// 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
2025-08-02 18:00:41 +02:00
static void SortExampleByName ( rlExampleInfo * items , int count ) ;
2025-08-01 00:30:01 +02:00
2025-08-04 17:51:48 +02:00
// Scan resource paths in example file
static char * * ScanExampleResources ( const char * filePath , int * resPathCount ) ;
// Clear resource paths scanned
static void ClearExampleResources ( char * * resPaths ) ;
2025-08-19 10:38:07 +02:00
// Add VS project (.vcxproj) to existing VS solution (.sln)
2025-08-13 12:33:12 +02:00
static int AddVSProjectToSolution ( const char * projFile , const char * slnFile , const char * category ) ;
2025-08-07 17:03:54 +02:00
2025-08-13 13:25:27 +02:00
// Generate unique UUID v4 string
// Output format: {9A2F48CC-0DA8-47C0-884E-02E37F9BE6C1}
const char * GenerateUUIDv4 ( void ) ;
2025-08-01 00:30:01 +02:00
//------------------------------------------------------------------------------------
// Program main entry point
//------------------------------------------------------------------------------------
int main ( int argc , char * argv [ ] )
{
2025-08-06 14:03:32 +02:00
// Path values can be configured with environment variables
exBasePath = getenv ( " REXM_EXAMPLES_BASE_PATH " ) ;
exWebPath = getenv ( " REXM_EXAMPLES_WEB_PATH " ) ;
exTemplateFilePath = getenv ( " REXM_EXAMPLES_TEMPLATE_FILE_PATH " ) ;
exTemplateScreenshot = getenv ( " REXM_EXAMPLES_TEMPLATE_SCREENSHOT_PATH " ) ;
exCollectionFilePath = getenv ( " REXM_EXAMPLES_COLLECTION_FILE_PATH " ) ;
2025-08-19 11:00:22 +02:00
exVSProjectSolutionFile = getenv ( " REXM_EXAMPLES_VS2022_SLN_FILE " ) ;
2025-08-06 14:03:32 +02:00
if ( ! exBasePath ) exBasePath = " C:/GitHub/raylib/examples " ;
if ( ! exWebPath ) exWebPath = " C:/GitHub/raylib.com/examples " ;
if ( ! exTemplateFilePath ) exTemplateFilePath = " C:/GitHub/raylib/examples/examples_template.c " ;
if ( ! exTemplateScreenshot ) exTemplateScreenshot = " C:/GitHub/raylib/examples/examples_template.png " ;
if ( ! exCollectionFilePath ) exCollectionFilePath = " C:/GitHub/raylib/examples/examples_list.txt " ;
2025-08-19 11:00:22 +02:00
if ( ! exVSProjectSolutionFile ) exVSProjectSolutionFile = " C:/GitHub/raylib/projects/VS2022/raylib.sln " ;
2025-08-06 14:03:32 +02:00
2025-08-02 00:36:17 +02:00
char inFileName [ 1024 ] = { 0 } ; // Example input filename (to be added)
2025-08-01 00:30:01 +02:00
2025-08-01 19:40:44 +02:00
char exName [ 64 ] = { 0 } ; // Example name, without extension: core_basic_window
2025-08-01 00:30:01 +02:00
char exCategory [ 32 ] = { 0 } ; // Example category: core
2025-08-03 22:56:15 +02:00
char exRecategory [ 32 ] = { 0 } ; // Example re-name category: shapes
2025-08-01 19:40:44 +02:00
char exRename [ 64 ] = { 0 } ; // Example re-name, without extension
2025-08-01 00:30:01 +02:00
int opCode = OP_NONE ; // Operation code: 0-None(Help), 1-Create, 2-Add, 3-Rename, 4-Remove
// Command-line usage mode
//--------------------------------------------------------------------------------------
if ( argc > 1 )
{
// Supported commands:
// help : Provides command-line usage information (default)
// create <new_example_name> : Creates an empty example, from internal template
// add <example_name> : Add existing example, category extracted from name
// rename <old_examples_name> <new_example_name> : Rename an existing example
// remove <example_name> : Remove an existing example
2025-08-01 19:40:44 +02:00
// validate : Validate examples collection
2025-08-01 00:30:01 +02:00
if ( strcmp ( argv [ 1 ] , " create " ) = = 0 )
{
// Check for valid upcoming argument
if ( argc = = 2 ) LOG ( " WARNING: No filename provided to create \n " ) ;
else if ( argc > 3 ) LOG ( " WARNING: Too many arguments provided \n " ) ;
else
{
2025-08-03 21:20:35 +02:00
// Security checks for file name to verify category is included
int catIndex = TextFindIndex ( argv [ 2 ] , " _ " ) ;
if ( catIndex > 3 )
{
char cat [ 12 ] = { 0 } ;
strncpy ( cat , argv [ 2 ] , catIndex ) ;
bool catFound = false ;
2025-08-05 12:37:11 +02:00
for ( int i = 0 ; i < REXM_MAX_EXAMPLE_CATEGORIES ; i + + )
2025-08-03 21:20:35 +02:00
{
if ( TextIsEqual ( cat , exCategories [ i ] ) ) { catFound = true ; break ; }
}
2025-08-01 19:40:44 +02:00
2025-08-03 21:20:35 +02:00
if ( catFound )
{
strcpy ( exName , argv [ 2 ] ) ; // Register filename for new example creation
strncpy ( exCategory , exName , TextFindIndex ( exName , " _ " ) ) ;
opCode = OP_CREATE ;
}
else LOG ( " WARNING: Example category is not valid \n " ) ;
}
else LOG ( " WARNING: Example name does not include category \n " ) ;
2025-08-01 00:30:01 +02:00
}
}
else if ( strcmp ( argv [ 1 ] , " add " ) = = 0 )
{
// Check for valid upcoming argument
if ( argc = = 2 ) LOG ( " WARNING: No filename provided to create \n " ) ;
else if ( argc > 3 ) LOG ( " WARNING: Too many arguments provided \n " ) ;
else
{
if ( IsFileExtension ( argv [ 2 ] , " .c " ) ) // Check for valid file extension: input
{
2025-08-02 00:36:17 +02:00
if ( FileExists ( inFileName ) )
{
2025-08-03 21:20:35 +02:00
// Security checks for file name to verify category is included
int catIndex = TextFindIndex ( argv [ 2 ] , " _ " ) ;
if ( catIndex > 3 )
{
char cat [ 12 ] = { 0 } ;
strncpy ( cat , argv [ 2 ] , catIndex ) ;
bool catFound = false ;
2025-08-05 12:37:11 +02:00
for ( int i = 0 ; i < REXM_MAX_EXAMPLE_CATEGORIES ; i + + )
2025-08-03 21:20:35 +02:00
{
if ( TextIsEqual ( cat , exCategories [ i ] ) ) { catFound = true ; break ; }
}
if ( catFound )
{
strcpy ( inFileName , argv [ 2 ] ) ; // Register filename for addition
strcpy ( exName , GetFileNameWithoutExt ( argv [ 2 ] ) ) ; // Register example name
strncpy ( exCategory , exName , TextFindIndex ( exName , " _ " ) ) ;
opCode = OP_ADD ;
}
else LOG ( " WARNING: Example category is not valid \n " ) ;
}
else LOG ( " WARNING: Example name does not include category \n " ) ;
2025-08-02 00:36:17 +02:00
}
else LOG ( " WARNING: Input file not found, include path \n " ) ;
2025-08-01 00:30:01 +02:00
}
else LOG ( " WARNING: Input file extension not recognized (.c) \ n " ) ;
}
}
else if ( strcmp ( argv [ 1 ] , " rename " ) = = 0 )
{
2025-08-02 00:36:17 +02:00
if ( argc = = 2 ) LOG ( " WARNING: No filename provided to be renamed \n " ) ;
2025-08-07 12:01:21 +01:00
else if ( argc = = 3 ) LOG ( " WARNING: No new filename provided to be renamed \n " ) ;
2025-08-01 19:40:44 +02:00
else if ( argc > 4 ) LOG ( " WARNING: Too many arguments provided \n " ) ;
2025-08-01 00:30:01 +02:00
else
{
2025-08-03 21:20:35 +02:00
// Verify example exists in collection to be removed
2025-08-04 19:27:48 +02:00
char * exColInfo = LoadFileText ( exCollectionFilePath ) ;
2025-08-03 21:20:35 +02:00
if ( TextFindIndex ( exColInfo , argv [ 2 ] ) ! = - 1 ) // Example in the collection
{
2025-08-07 12:39:12 +01:00
// Security checks for new file name to verify category is included
int newCatIndex = TextFindIndex ( argv [ 3 ] , " _ " ) ;
if ( newCatIndex > 3 )
{
char cat [ 12 ] = { 0 } ;
strncpy ( cat , argv [ 3 ] , newCatIndex ) ;
bool newCatFound = false ;
for ( int i = 0 ; i < REXM_MAX_EXAMPLE_CATEGORIES ; i + + )
{
if ( TextIsEqual ( cat , exCategories [ i ] ) ) { newCatFound = true ; break ; }
}
if ( newCatFound )
{
strcpy ( exName , argv [ 2 ] ) ; // Register example name
strncpy ( exCategory , exName , TextFindIndex ( exName , " _ " ) ) ;
strcpy ( exRename , argv [ 3 ] ) ;
strncpy ( exRecategory , exRename , TextFindIndex ( exRename , " _ " ) ) ;
opCode = OP_RENAME ;
}
else LOG ( " WARNING: Example new category is not valid \n " ) ;
}
else LOG ( " WARNING: Example new name does not include category \n " ) ;
2025-08-03 21:20:35 +02:00
}
else LOG ( " WARNING: RENAME: Example not available in the collection \n " ) ;
UnloadFileText ( exColInfo ) ;
2025-08-01 00:30:01 +02:00
}
}
else if ( strcmp ( argv [ 1 ] , " remove " ) = = 0 )
{
// Check for valid upcoming argument
if ( argc = = 2 ) LOG ( " WARNING: No filename provided to create \n " ) ;
else if ( argc > 3 ) LOG ( " WARNING: Too many arguments provided \n " ) ;
else
{
2025-08-03 21:20:35 +02:00
// Verify example exists in collection to be removed
2025-08-04 19:27:48 +02:00
char * exColInfo = LoadFileText ( exCollectionFilePath ) ;
2025-08-03 21:20:35 +02:00
if ( TextFindIndex ( exColInfo , argv [ 2 ] ) ! = - 1 ) // Example in the collection
{
strcpy ( exName , argv [ 2 ] ) ; // Register filename for removal
2025-08-03 23:32:54 +02:00
strncpy ( exCategory , exName , TextFindIndex ( exName , " _ " ) ) ;
2025-08-03 21:20:35 +02:00
opCode = OP_REMOVE ;
}
else LOG ( " WARNING: REMOVE: Example not available in the collection \n " ) ;
UnloadFileText ( exColInfo ) ;
2025-08-01 00:30:01 +02:00
}
}
2025-08-01 19:40:44 +02:00
else if ( strcmp ( argv [ 1 ] , " validate " ) = = 0 )
{
2025-08-05 12:37:11 +02:00
// Validate examples in collection (report results)
// All examples in collection match all files requirements
2025-08-03 21:20:35 +02:00
opCode = OP_VALIDATE ;
2025-08-01 19:40:44 +02:00
}
2025-08-05 12:38:11 +02:00
else if ( strcmp ( argv [ 1 ] , " update " ) = = 0 )
{
// Validate and update examples in collection
// All examples in collection match all files requirements
opCode = OP_UPDATE ;
}
2025-08-01 00:30:01 +02:00
}
2025-08-01 19:40:44 +02:00
2025-08-01 00:30:01 +02:00
switch ( opCode )
{
2025-08-05 12:37:11 +02:00
case OP_CREATE : // Create: New example from template
2025-08-01 00:30:01 +02:00
{
2025-08-02 00:36:17 +02:00
// Create: raylib/examples/<category>/<category>_example_name.c
2025-08-02 18:00:41 +02:00
char * exText = LoadFileText ( exTemplateFilePath ) ;
char * exTextUpdated [ 6 ] = { 0 } ;
int exIndex = TextFindIndex ( exText , " /**************** " ) ;
2025-08-03 09:43:38 +02:00
// Update required info with some defaults
2025-08-02 18:00:41 +02:00
exTextUpdated [ 0 ] = TextReplace ( exText + exIndex , " <module> " , exCategory ) ;
exTextUpdated [ 1 ] = TextReplace ( exTextUpdated [ 0 ] , " <name> " , exName + strlen ( exCategory ) + 1 ) ;
//TextReplace(newExample, "<user_name>", "Ray");
//TextReplace(newExample, "@<user_github>", "@raysan5");
//TextReplace(newExample, "<year_created>", 2025);
//TextReplace(newExample, "<year_updated>", 2025);
SaveFileText ( TextFormat ( " %s/%s/%s.c " , exBasePath , exCategory , exName ) , exTextUpdated [ 1 ] ) ;
for ( int i = 0 ; i < 6 ; i + + ) { MemFree ( exTextUpdated [ i ] ) ; exTextUpdated [ i ] = NULL ; }
UnloadFileText ( exText ) ;
2025-08-01 00:30:01 +02:00
}
2025-08-05 12:37:11 +02:00
case OP_ADD : // Add: Example from command-line input filename
2025-08-01 00:30:01 +02:00
{
2025-08-03 09:43:38 +02:00
// Add: raylib/examples/<category>/<category>_example_name.c
2025-08-02 00:36:17 +02:00
if ( opCode ! = 1 ) FileCopy ( inFileName , TextFormat ( " %s/%s/%s.c " , exBasePath , exCategory , exName ) ) ;
2025-08-01 00:30:01 +02:00
// Create: raylib/examples/<category>/<category>_example_name.png
2025-08-02 00:36:17 +02:00
FileCopy ( exTemplateScreenshot , TextFormat ( " %s/%s/%s.png " , exBasePath , exCategory , exName ) ) ; // WARNING: To be updated manually!
2025-08-01 19:40:44 +02:00
2025-08-04 17:51:48 +02:00
// Copy: raylib/examples/<category>/resources/...
// -----------------------------------------------------------------------------------------
// Scan resources used in example to copy
int resPathCount = 0 ;
2025-08-04 18:11:57 +02:00
char * * resPaths = ScanExampleResources ( TextFormat ( " %s/%s/%s.c " , exBasePath , exCategory , exName ) , & resPathCount ) ;
2025-08-04 17:51:48 +02:00
if ( resPathCount > 0 )
{
for ( int r = 0 ; r < resPathCount ; 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 ] ) ) ;
LOG ( " INFO: Example resource required: %s \n " , resPathUpdated ) ;
if ( FileExists ( TextFormat ( " %s/%s " , GetDirectoryPath ( inFileName ) , resPathUpdated ) ) )
{
// Verify the resources are placed in "resources" directory
if ( TextFindIndex ( resPathUpdated , " resources/ " ) > 0 )
{
// NOTE: Look for resources in the path of the provided .c to be added
// To be copied to <category>/resources directory, extra dirs are automatically created if required
FileCopy ( TextFormat ( " %s/%s " , GetDirectoryPath ( inFileName ) , resPathUpdated ) ,
TextFormat ( " %s/%s/%s " , exBasePath , exCategory , resPathUpdated ) ) ;
}
else LOG ( " WARNING: Example resource must be placed in 'resources' directory next to .c file \n " ) ;
}
else LOG ( " WARNING: Example resource can not be found in: %s \n " , TextFormat ( " %s/%s " , GetDirectoryPath ( inFileName ) , resPathUpdated ) ) ;
RL_FREE ( resPathUpdated ) ;
}
}
else
{
LOG ( " INFO: Example resource required: %s \n " , resPaths [ r ] ) ;
if ( FileExists ( TextFormat ( " %s/%s " , GetDirectoryPath ( inFileName ) , resPaths [ r ] ) ) )
{
// Verify the resources are placed in "resources" directory
if ( TextFindIndex ( resPaths [ r ] , " resources/ " ) > 0 )
{
// NOTE: Look for resources in the path of the provided .c to be added
// To be copied to <category>/resources directory, extra dirs are automatically created if required
FileCopy ( TextFormat ( " %s/%s " , GetDirectoryPath ( inFileName ) , resPaths [ r ] ) ,
TextFormat ( " %s/%s/%s " , exBasePath , exCategory , resPaths [ r ] ) ) ;
}
else LOG ( " WARNING: Example resource must be placed in 'resources' directory next to .c file \n " ) ;
}
else LOG ( " WARNING: Example resource can not be found in: %s \n " , TextFormat ( " %s/%s " , GetDirectoryPath ( inFileName ) , resPaths [ r ] ) ) ;
}
}
}
ClearExampleResources ( resPaths ) ;
// -----------------------------------------------------------------------------------------
2025-08-02 00:36:17 +02:00
2025-08-03 23:49:52 +02:00
// Add example to the collection list, if not already there
2025-08-02 18:00:41 +02:00
// NOTE: Required format: shapes;shapes_basic_shapes;⭐️☆☆☆;1.0;4.2;"Ray";@raysan5
//------------------------------------------------------------------------------------------------
2025-08-05 09:54:17 +02:00
char * exCollectionList = LoadFileText ( exCollectionFilePath ) ;
if ( TextFindIndex ( exCollectionList , exName ) = = - 1 ) // Example not found
2025-08-02 18:00:41 +02:00
{
2025-08-13 12:33:38 +02:00
char * exCollectionListUpdated = ( char * ) RL_CALLOC ( REXM_MAX_BUFFER_SIZE , 1 ) ; // Updated list copy, 2MB
2025-08-02 18:00:41 +02:00
// Add example to the main list, by category
// by default add it last in the category list
// NOTE: When populating to other files, lists are sorted by name
2025-08-05 09:54:17 +02:00
int nextCategoryIndex = 0 ;
if ( strcmp ( exCategory , " core " ) = = 0 ) nextCategoryIndex = 1 ;
else if ( strcmp ( exCategory , " shapes " ) = = 0 ) nextCategoryIndex = 2 ;
else if ( strcmp ( exCategory , " textures " ) = = 0 ) nextCategoryIndex = 3 ;
else if ( strcmp ( exCategory , " text " ) = = 0 ) nextCategoryIndex = 4 ;
else if ( strcmp ( exCategory , " models " ) = = 0 ) nextCategoryIndex = 5 ;
else if ( strcmp ( exCategory , " shaders " ) = = 0 ) nextCategoryIndex = 6 ;
else if ( strcmp ( exCategory , " audio " ) = = 0 ) nextCategoryIndex = 7 ;
else if ( strcmp ( exCategory , " others " ) = = 0 ) nextCategoryIndex = - 1 ; // Add to EOF
// Get required example info from example file header (if provided)
2025-08-03 23:49:52 +02:00
// NOTE: If no example info is provided (other than category/name), just using some default values
2025-08-06 14:04:30 +02:00
rlExampleInfo * exInfo = LoadExampleInfo ( TextFormat ( " %s/%s/%s.c " , exBasePath , exCategory , exName ) ) ;
2025-08-05 09:54:17 +02:00
// Get example difficulty stars
char starsText [ 16 ] = { 0 } ;
2025-08-06 00:56:29 +02:00
for ( int i = 0 ; i < 4 ; i + + )
2025-08-05 09:54:17 +02:00
{
2025-08-06 00:56:29 +02:00
// NOTE: Every UTF-8 star are 3 bytes
2025-08-06 18:14:52 +02:00
if ( i < exInfo - > stars ) strcpy ( starsText + 3 * i , " ★ " ) ;
else strcpy ( starsText + 3 * i , " ☆ " ) ;
2025-08-05 09:54:17 +02:00
}
if ( nextCategoryIndex = = - 1 )
2025-08-02 18:00:41 +02:00
{
2025-08-03 23:49:52 +02:00
// Add example to collection at the EOF
2025-08-05 09:54:17 +02:00
int endIndex = ( int ) strlen ( exCollectionList ) ;
memcpy ( exCollectionListUpdated , exCollectionList , endIndex ) ;
2025-08-06 00:56:29 +02:00
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 ) ) ;
2025-08-02 18:00:41 +02:00
}
else
{
2025-08-03 23:49:52 +02:00
// Add example to collection, at the end of the category list
2025-08-05 09:54:17 +02:00
int categoryIndex = TextFindIndex ( exCollectionList , exCategories [ nextCategoryIndex ] ) ;
memcpy ( exCollectionListUpdated , exCollectionList , categoryIndex ) ;
2025-08-06 00:56:29 +02:00
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 ) ) ;
2025-08-05 09:54:17 +02:00
memcpy ( exCollectionListUpdated + categoryIndex + textWritenSize , exCollectionList + categoryIndex , strlen ( exCollectionList ) - categoryIndex ) ;
2025-08-02 18:00:41 +02:00
}
2025-08-06 00:56:29 +02:00
2025-08-06 14:04:30 +02:00
UnloadExampleInfo ( exInfo ) ;
2025-08-02 18:00:41 +02:00
2025-08-05 09:54:17 +02:00
SaveFileText ( exCollectionFilePath , exCollectionListUpdated ) ;
RL_FREE ( exCollectionListUpdated ) ;
2025-08-02 18:00:41 +02:00
}
2025-08-03 21:20:35 +02:00
else LOG ( " WARNING: ADD: Example is already on the collection \n " ) ;
2025-08-05 09:54:17 +02:00
UnloadFileText ( exCollectionList ) ;
2025-08-02 18:00:41 +02:00
//------------------------------------------------------------------------------------------------
2025-08-01 19:40:44 +02:00
2025-08-03 22:56:15 +02:00
// Update: Makefile, Makefile.Web, README.md, examples.js
2025-08-02 18:00:41 +02:00
//------------------------------------------------------------------------------------------------
2025-08-03 22:56:15 +02:00
UpdateRequiredFiles ( ) ;
2025-08-02 18:00:41 +02:00
//------------------------------------------------------------------------------------------------
2025-08-01 00:30:01 +02:00
// Create: raylib/projects/VS2022/examples/<category>_example_name.vcxproj
2025-08-02 18:00:41 +02:00
//------------------------------------------------------------------------------------------------
2025-08-07 17:03:54 +02:00
// WARNING: When adding new project a unique UUID should be assigned!
2025-08-02 00:36:17 +02:00
FileCopy ( TextFormat ( " %s/../projects/VS2022/examples/core_basic_window.vcxproj " , exBasePath ) ,
TextFormat ( " %s/../projects/VS2022/examples/%s.vcxproj " , exBasePath , exName ) ) ;
2025-08-02 18:00:41 +02:00
FileTextReplace ( TextFormat ( " %s/../projects/VS2022/examples/%s.vcxproj " , exBasePath , exName ) ,
" core_basic_window " , exName ) ;
FileTextReplace ( TextFormat ( " %s/../projects/VS2022/examples/%s.vcxproj " , exBasePath , exName ) ,
" .. \\ .. \\ examples \\ core " , TextFormat ( " .. \\ .. \\ examples \\ %s " , exCategory ) ) ;
2025-08-02 00:36:17 +02:00
// Edit: raylib/projects/VS2022/raylib.sln --> Add new example project
2025-08-13 12:33:12 +02:00
// WARNING: This function uses TextFormat() extensively inside,
// we must store provided file paths because pointers will be overwriten
2025-08-07 17:03:54 +02:00
AddVSProjectToSolution ( TextFormat ( " %s/../projects/VS2022/examples/%s.vcxproj " , exBasePath , exName ) ,
2025-08-13 12:33:12 +02:00
exVSProjectSolutionFile , exCategory ) ;
2025-08-02 18:00:41 +02:00
//------------------------------------------------------------------------------------------------
2025-08-02 00:36:17 +02:00
// Recompile example (on raylib side)
// NOTE: Tools requirements: emscripten, w64devkit
2025-08-01 00:30:01 +02:00
// Compile to: raylib.com/examples/<category>/<category>_example_name.html
// Compile to: raylib.com/examples/<category>/<category>_example_name.data
// Compile to: raylib.com/examples/<category>/<category>_example_name.wasm
// Compile to: raylib.com/examples/<category>/<category>_example_name.js
2025-08-03 22:56:15 +02:00
//------------------------------------------------------------------------------------------------
2025-08-05 09:54:51 +02:00
// TODO: Avoid platform-specific .BAT file
/*
SET RAYLIB_PATH = C : \ GitHub \ raylib
SET COMPILER_PATH = C : \ raylib \ w64devkit \ bin
ENV_SET PATH = $ ( COMPILER_PATH )
SET MAKE = mingw32 - make
$ ( MAKE ) - f Makefile . Web shaders / shaders_deferred_render PLATFORM = $ ( PLATFORM ) - B
//int putenv(char *string); // putenv takes a string of the form NAME=VALUE
//int setenv(const char *envname, const char *envval, int overwrite);
//int unsetenv(const char *name); //unset variable
putenv ( " RAYLIB_DIR=C: \\ GitHub \\ raylib " ) ;
putenv ( " PATH=%PATH%;C: \\ raylib \\ w64devkit \\ bin " ) ;
setenv ( " RAYLIB_DIR " , " C: \\ GitHub \\ raylib " , 1 ) ;
unsetenv ( " RAYLIB_DIR " ) ;
getenv ( " RAYLIB_DIR " ) ;
system ( TextFormat ( " make -f Makefile.Web %s/%s PLATFORM=PLATFORM_WEB -B " , exCategory , exName ) ) ;
*/
2025-08-02 19:21:36 +02:00
system ( TextFormat ( " %s/build_example_web.bat %s/%s " , exBasePath , exCategory , exName ) ) ;
2025-08-01 00:30:01 +02:00
// Copy results to web side
FileCopy ( TextFormat ( " %s/%s/%s.html " , exBasePath , exCategory , exName ) ,
TextFormat ( " %s/%s/%s.html " , exWebPath , exCategory , exName ) ) ;
FileCopy ( TextFormat ( " %s/%s/%s.data " , exBasePath , exCategory , exName ) ,
TextFormat ( " %s/%s/%s.data " , exWebPath , exCategory , exName ) ) ;
FileCopy ( TextFormat ( " %s/%s/%s.wasm " , exBasePath , exCategory , exName ) ,
TextFormat ( " %s/%s/%s.wasm " , exWebPath , exCategory , exName ) ) ;
FileCopy ( TextFormat ( " %s/%s/%s.js " , exBasePath , exCategory , exName ) ,
TextFormat ( " %s/%s/%s.js " , exWebPath , exCategory , exName ) ) ;
2025-08-03 22:56:15 +02:00
//------------------------------------------------------------------------------------------------
2025-08-01 00:30:01 +02:00
} break ;
2025-08-05 12:37:11 +02:00
case OP_RENAME : // Rename
2025-08-01 00:30:01 +02:00
{
2025-08-03 22:56:15 +02:00
// NOTE: At this point provided values have been validated:
// exName, exCategory, exRename, exRecategory
if ( strcmp ( exCategory , exRecategory ) = = 0 )
{
// Rename example on collection
2025-08-04 19:27:48 +02:00
FileTextReplace ( exCollectionFilePath , TextFormat ( " %s;%s " , exCategory , exName ) ,
2025-08-03 22:56:15 +02:00
TextFormat ( " %s;%s " , exRecategory , exRename ) ) ;
2025-08-04 18:11:57 +02:00
// Edit: Rename example code and screenshot files .c and .png
2025-08-21 19:37:59 +02:00
FileRename ( TextFormat ( " %s/%s/%s.c " , exBasePath , exCategory , exName ) ,
2025-08-03 22:56:15 +02:00
TextFormat ( " %s/%s/%s.c " , exBasePath , exCategory , exRename ) ) ;
2025-08-21 19:37:59 +02:00
FileRename ( TextFormat ( " %s/%s/%s.png " , exBasePath , exCategory , exName ) ,
2025-08-03 22:56:15 +02:00
TextFormat ( " %s/%s/%s.png " , exBasePath , exCategory , exRename ) ) ;
2025-08-04 18:11:57 +02:00
// NOTE: Example resource files do not need to be changed...
// unless the example is moved from one caegory to another
// Edit: Rename example on required files
2025-08-03 22:56:15 +02:00
FileTextReplace ( TextFormat ( " %s/Makefile " , exBasePath ) , exName , exRename ) ;
FileTextReplace ( TextFormat ( " %s/Makefile.Web " , exBasePath ) , exName , exRename ) ;
FileTextReplace ( TextFormat ( " %s/README.md " , exBasePath ) , exName , exRename ) ;
2025-08-18 15:00:54 +02:00
FileTextReplace ( TextFormat ( " %s/../common/examples.js " , exWebPath ) ,
exName + strlen ( exCategory ) + 1 , exRename + strlen ( exRecategory ) + 1 ) ; // Skip category
2025-08-03 22:56:15 +02:00
2025-08-04 18:11:57 +02:00
// Edit: Rename example project and solution
2025-08-21 19:30:44 +02:00
FileTextReplace ( TextFormat ( " %s/../projects/VS2022/examples/%s.vcxproj " , exBasePath , exName ) , exName , exRename ) ;
FileRename ( TextFormat ( " %s/../projects/VS2022/examples/%s.vcxproj " , exBasePath , exName ) ,
2025-08-03 22:56:15 +02:00
TextFormat ( " %s/../projects/VS2022/examples/%s.vcxproj " , exBasePath , exRename ) ) ;
FileTextReplace ( TextFormat ( " %s/../projects/VS2022/raylib.sln " , exBasePath ) , exName , exRename ) ;
}
else
{
2025-08-04 18:11:57 +02:00
// WARNING: Rename with change of category
// TODO: Reorder collection to place renamed example at the end of category
2025-08-04 19:27:48 +02:00
FileTextReplace ( exCollectionFilePath , TextFormat ( " %s;%s " , exCategory , exName ) ,
2025-08-03 22:56:15 +02:00
TextFormat ( " %s;%s " , exRecategory , exRename ) ) ;
2025-08-04 18:11:57 +02:00
// TODO: Move example resources from <src_category>/resources to <dst_category>/resources
// WARNING: Resources can be shared with other examples in the category
// Edit: Rename example code file (copy and remove)
2025-08-03 22:56:15 +02:00
FileCopy ( TextFormat ( " %s/%s/%s.c " , exBasePath , exCategory , exName ) ,
TextFormat ( " %s/%s/%s.c " , exBasePath , exCategory , exRename ) ) ;
2025-08-21 19:37:59 +02:00
FileRemove ( TextFormat ( " %s/%s/%s.c " , exBasePath , exCategory , exName ) ) ;
2025-08-04 18:11:57 +02:00
// Edit: Rename example screenshot file (copy and remove)
2025-08-03 22:56:15 +02:00
FileCopy ( TextFormat ( " %s/%s/%s.png " , exBasePath , exCategory , exName ) ,
TextFormat ( " %s/%s/%s.png " , exBasePath , exCategory , exRename ) ) ;
2025-08-21 19:37:59 +02:00
FileRemove ( TextFormat ( " %s/%s/%s.png " , exBasePath , exCategory , exName ) ) ;
2025-08-01 19:40:44 +02:00
2025-08-04 18:11:57 +02:00
// Edit: Update required files: Makefile, Makefile.Web, README.md, examples.js
2025-08-03 22:56:15 +02:00
UpdateRequiredFiles ( ) ;
}
2025-08-01 19:40:44 +02:00
2025-08-01 00:30:01 +02:00
// Remove old web compilation
2025-08-21 19:37:59 +02:00
FileRemove ( TextFormat ( " %s/%s/%s.html " , exWebPath , exCategory , exName ) ) ;
FileRemove ( TextFormat ( " %s/%s/%s.data " , exWebPath , exCategory , exName ) ) ;
FileRemove ( TextFormat ( " %s/%s/%s.wasm " , exWebPath , exCategory , exName ) ) ;
FileRemove ( TextFormat ( " %s/%s/%s.js " , exWebPath , exCategory , exName ) ) ;
2025-08-01 00:30:01 +02:00
// Recompile example (on raylib side)
// NOTE: Tools requirements: emscripten, w64devkit
2025-08-03 22:56:15 +02:00
system ( TextFormat ( " %s/build_example_web.bat %s/%s " , exBasePath , exRecategory , exRename ) ) ;
2025-08-01 00:30:01 +02:00
// Copy results to web side
2025-08-03 22:56:15 +02:00
FileCopy ( TextFormat ( " %s/%s/%s.html " , exBasePath , exRecategory , exRename ) ,
TextFormat ( " %s/%s/%s.html " , exWebPath , exRecategory , exRename ) ) ;
FileCopy ( TextFormat ( " %s/%s/%s.data " , exBasePath , exRecategory , exRename ) ,
TextFormat ( " %s/%s/%s.data " , exWebPath , exRecategory , exRename ) ) ;
FileCopy ( TextFormat ( " %s/%s/%s.wasm " , exBasePath , exRecategory , exRename ) ,
TextFormat ( " %s/%s/%s.wasm " , exWebPath , exRecategory , exRename ) ) ;
FileCopy ( TextFormat ( " %s/%s/%s.js " , exBasePath , exRecategory , exRename ) ,
TextFormat ( " %s/%s/%s.js " , exWebPath , exRecategory , exRename ) ) ;
2025-08-01 00:30:01 +02:00
} break ;
2025-08-05 12:37:11 +02:00
case OP_REMOVE : // Remove
2025-08-01 00:30:01 +02:00
{
2025-08-03 23:32:54 +02:00
// Remove example from collection for files update
//------------------------------------------------------------------------------------------------
2025-08-05 12:37:11 +02:00
char * exCollectionList = LoadFileText ( exCollectionFilePath ) ;
int exIndex = TextFindIndex ( exCollectionList , TextFormat ( " %s;%s " , exCategory , exName ) ) ;
2025-08-03 23:32:54 +02:00
if ( exIndex > 0 ) // Example found
{
2025-08-13 12:33:12 +02:00
char * exCollectionListUpdated = ( char * ) RL_CALLOC ( REXM_MAX_BUFFER_SIZE , 1 ) ; // Updated list copy, 2MB
2025-08-03 23:32:54 +02:00
2025-08-05 12:37:11 +02:00
memcpy ( exCollectionListUpdated , exCollectionList , exIndex ) ;
2025-08-03 23:32:54 +02:00
int lineLen = 0 ;
2025-08-05 12:37:11 +02:00
for ( int i = exIndex ; ( exCollectionList [ i ] ! = ' \n ' ) & & ( exCollectionList [ i ] ! = ' \0 ' ) ; i + + ) lineLen + + ;
2025-08-03 23:32:54 +02:00
// Remove line and copy the rest next
2025-08-05 12:37:11 +02:00
memcpy ( exCollectionListUpdated + exIndex , exCollectionList + exIndex + lineLen + 1 , strlen ( exCollectionList ) - exIndex - lineLen ) ;
2025-08-03 23:32:54 +02:00
2025-08-05 12:37:11 +02:00
SaveFileText ( exCollectionFilePath , exCollectionListUpdated ) ;
RL_FREE ( exCollectionListUpdated ) ;
2025-08-03 23:32:54 +02:00
}
else LOG ( " WARNING: REMOVE: Example not found in the collection \n " ) ;
2025-08-05 12:37:11 +02:00
UnloadFileText ( exCollectionList ) ;
2025-08-03 23:32:54 +02:00
//------------------------------------------------------------------------------------------------
2025-08-04 18:11:57 +02:00
// Remove: raylib/examples/<category>/resources/..
// WARNING: Some of those resources could be used by other examples,
// just leave this process to manual update for now!
// -----------------------------------------------------------------------------------------
/*
// Scan resources used in example to be removed
int resPathCount = 0 ;
char * * resPaths = ScanExampleResources ( TextFormat ( " %s/%s/%s.c " , exBasePath , exCategory , exName ) , & resPathCount ) ;
if ( resPathCount > 0 )
{
for ( int r = 0 ; r < resPathCount ; 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 ] ) ) ;
2025-08-21 19:37:59 +02:00
FileRemove ( TextFormat ( " %s/%s/%s " , exBasePath , exCategory , resPathUpdated ) ) ;
2025-08-04 18:11:57 +02:00
RL_FREE ( resPathUpdated ) ;
}
}
2025-08-21 19:37:59 +02:00
else FileRemove ( TextFormat ( " %s/%s/%s " , exBasePath , exCategory , resPaths [ r ] ) ) ;
2025-08-04 18:11:57 +02:00
}
}
ClearExampleResources ( resPaths ) ;
*/
// -----------------------------------------------------------------------------------------
2025-08-01 00:30:01 +02:00
2025-08-03 22:56:15 +02:00
// Remove: raylib/examples/<category>/<category>_example_name.c
// Remove: raylib/examples/<category>/<category>_example_name.png
2025-08-21 19:37:59 +02:00
FileRemove ( TextFormat ( " %s/%s/%s.c " , exBasePath , exCategory , exName ) ) ;
FileRemove ( TextFormat ( " %s/%s/%s.png " , exBasePath , exCategory , exName ) ) ;
2025-08-03 22:56:15 +02:00
2025-08-04 18:11:57 +02:00
// Edit: Update required files: Makefile, Makefile.Web, README.md, examples.js
2025-08-03 23:32:54 +02:00
UpdateRequiredFiles ( ) ;
2025-08-03 22:56:15 +02:00
// Remove: raylib/projects/VS2022/examples/<category>_example_name.vcxproj
2025-08-21 19:37:59 +02:00
FileRemove ( TextFormat ( " %s/../projects/VS2022/examples/%s.vcxproj " , exBasePath , exName ) ) ;
2025-08-03 22:56:15 +02:00
2025-08-19 10:38:07 +02:00
// Edit: raylib/projects/VS2022/raylib.sln --> Remove example project
2025-08-03 22:56:15 +02:00
//---------------------------------------------------------------------------
2025-08-19 10:38:07 +02:00
// TODO: Remove project from solution
2025-08-07 17:03:54 +02:00
//RemoveVSProjectFromSolution(TextFormat("%s/../projects/VS2022/examples/%s.vcxproj", exBasePath, exName),
// TextFormat("%s/../projects/VS2022/raylib.sln", exBasePath));
2025-08-03 22:56:15 +02:00
//---------------------------------------------------------------------------
// Remove: raylib.com/examples/<category>/<category>_example_name.html
// Remove: raylib.com/examples/<category>/<category>_example_name.data
// Remove: raylib.com/examples/<category>/<category>_example_name.wasm
// Remove: raylib.com/examples/<category>/<category>_example_name.js
2025-08-21 19:37:59 +02:00
FileRemove ( TextFormat ( " %s/%s/%s.html " , exWebPath , exCategory , exName ) ) ;
FileRemove ( TextFormat ( " %s/%s/%s.data " , exWebPath , exCategory , exName ) ) ;
FileRemove ( TextFormat ( " %s/%s/%s.wasm " , exWebPath , exCategory , exName ) ) ;
FileRemove ( TextFormat ( " %s/%s/%s.js " , exWebPath , exCategory , exName ) ) ;
2025-08-03 22:56:15 +02:00
2025-08-01 19:40:44 +02:00
} break ;
2025-08-05 12:37:11 +02:00
case OP_VALIDATE : // Validate: report and actions
2025-08-05 12:38:11 +02:00
case OP_UPDATE :
2025-08-01 19:40:44 +02:00
{
2025-08-06 00:56:29 +02:00
/*
// 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
*/
2025-08-08 21:54:15 +02:00
// TODO: Log more details about the validation process
2025-08-14 20:38:27 +02:00
// Scan available example .c files and add to collection missing ones
// NOTE: Source of truth is what we have in the examples directories (on validation/update)
FilePathList list = LoadDirectoryFilesEx ( exBasePath , " .c " , true ) ;
2025-08-19 10:38:07 +02:00
char * exList = LoadFileText ( exCollectionFilePath ) ;
2025-08-14 20:38:27 +02:00
char * exListUpdated = ( char * ) RL_CALLOC ( REXM_MAX_BUFFER_SIZE , 1 ) ;
bool listUpdated = false ;
int exListLen = strlen ( exList ) ;
strcpy ( exListUpdated , exList ) ;
for ( int i = 0 ; i < list . count ; i + + )
{
if ( ( strcmp ( " examples_template " , GetFileNameWithoutExt ( list . paths [ i ] ) ) ! = 0 ) & & // HACK: Skip "examples_template"
( TextFindIndex ( exList , GetFileNameWithoutExt ( list . paths [ i ] ) ) = = - 1 ) )
{
// Add example to the examples collection list
// WARNING: Added to the end of the list, order must be set by users and
// defines placement on raylib webpage
rlExampleInfo * exInfo = LoadExampleInfo ( list . paths [ i ] ) ;
// Get example difficulty stars
char starsText [ 16 ] = { 0 } ;
for ( int i = 0 ; i < 4 ; i + + )
{
// NOTE: Every UTF-8 star are 3 bytes
2025-08-14 21:07:36 +02:00
if ( i < exInfo - > stars ) strcpy ( starsText + 3 * i , " ⭐️ " ) ;
2025-08-14 20:38:27 +02:00
else strcpy ( starsText + 3 * i , " ☆ " ) ;
}
exListLen + = sprintf ( exListUpdated + exListLen ,
2025-08-14 21:07:36 +02:00
TextFormat ( " %s;%s;%s;%.1f;%.1f; \" %s \" ;@%s \n " ,
2025-08-14 20:38:27 +02:00
exInfo - > category , exInfo - > name , starsText , exInfo - > verCreated ,
exInfo - > verUpdated , exInfo - > author , exInfo - > authorGitHub ) ) ;
listUpdated = true ;
UnloadExampleInfo ( exInfo ) ;
}
}
if ( listUpdated ) SaveFileText ( exCollectionFilePath , exListUpdated ) ;
UnloadFileText ( exList ) ;
RL_FREE ( exListUpdated ) ;
UnloadDirectoryFiles ( list ) ;
2025-08-06 00:56:29 +02:00
// Check all examples in collection [examples_list.txt] -> Source of truth!
int exCollectionCount = 0 ;
2025-08-06 15:43:13 +02:00
rlExampleInfo * exCollection = LoadExamplesData ( exCollectionFilePath , " ALL " , false , & exCollectionCount ) ;
2025-08-06 00:56:29 +02:00
// TODO: Validate: Duplicate entries in collection list?
2025-08-06 14:04:30 +02:00
// Set status information for all examples, using "status" field in the struct
2025-08-06 00:56:29 +02:00
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 ) ) ;
2025-08-06 14:04:30 +02:00
if ( memcmp ( imScreenshot . data , imTemplate . data , GetPixelDataSize ( imScreenshot . width , imScreenshot . height , imScreenshot . format ) ) = = 0 )
exInfo - > status | = VALID_INVALID_PNG ;
2025-08-06 00:56:29 +02:00
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?
2025-08-06 14:04:30 +02:00
if ( FileTextFind ( TextFormat ( " %s/README.md " , exBasePath ) , exInfo - > name ) = = - 1 ) exInfo - > status | = VALID_NOT_IN_README ;
2025-08-06 00:56:29 +02:00
// Validate: raylib.com/common/examples.js -> Example listed?
2025-08-06 14:04:30 +02:00
if ( FileTextFind ( TextFormat ( " %s/../common/examples.js " , exWebPath ) , exInfo - > name + TextFindIndex ( exInfo - > name , " _ " ) + 1 ) = = - 1 ) exInfo - > status | = VALID_NOT_IN_JS ;
2025-08-06 00:56:29 +02:00
// Validate: raylib/projects/VS2022/examples/<category>_example_name.vcxproj -> File exists?
2025-08-06 14:04:30 +02:00
if ( ! FileExists ( TextFormat ( " %s/../projects/VS2022/examples/%s.vcxproj " , exBasePath , exInfo - > name ) ) ) exInfo - > status | = VALID_MISSING_VCXPROJ ;
2025-08-06 00:56:29 +02:00
// 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
2025-08-17 21:30:16 +02:00
// WARNING: Some paths could be for files to save, not files to load, verify it
2025-08-06 00:56:29 +02:00
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 ] ) ) ;
2025-08-06 14:04:30 +02:00
if ( ! FileExists ( TextFormat ( " %s/%s/%s " , exBasePath , exInfo - > category , resPathUpdated ) ) )
{
exInfo - > status | = VALID_MISSING_RESOURCES ;
// Logging missing resources for convenience
LOG ( " WARNING: [%s] Missing resource: %s \n " , exInfo - > name , resPathUpdated ) ;
}
2025-08-06 00:56:29 +02:00
RL_FREE ( resPathUpdated ) ;
}
}
else
{
2025-08-06 14:04:30 +02:00
if ( ! FileExists ( TextFormat ( " %s/%s/%s " , exBasePath , exInfo - > category , resPaths [ r ] ) ) )
{
exInfo - > status | = VALID_MISSING_RESOURCES ;
LOG ( " WARNING: [%s] Missing resource: %s \n " , exInfo - > name , resPaths [ r ] ) ;
}
2025-08-06 00:56:29 +02:00
}
}
}
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?
2025-08-17 21:30:16 +02:00
if ( ! TextIsEqual ( exInfo - > category , " others " ) & &
( ! FileExists ( TextFormat ( " %s/%s/%s.html " , exWebPath , exInfo - > category , exInfo - > name ) ) | |
! FileExists ( TextFormat ( " %s/%s/%s.wasm " , exWebPath , exInfo - > category , exInfo - > name ) ) | |
! FileExists ( TextFormat ( " %s/%s/%s.js " , exWebPath , exInfo - > category , exInfo - > name ) ) | |
( ( exInfo - > resCount > 0 ) & & ! FileExists ( TextFormat ( " %s/%s/%s.data " , exWebPath , exInfo - > category , exInfo - > name ) ) ) ) )
{
2025-08-06 14:04:30 +02:00
exInfo - > status | = VALID_MISSING_WEB_OUTPUT ;
2025-08-17 21:30:16 +02:00
}
2025-08-06 00:56:29 +02:00
// NOTE: Additional validation elements
// Validate: Example naming conventions: <category>/<category>_example_name, valid category
if ( ( TextFindIndex ( exInfo - > name , exInfo - > category ) = = - 1 ) | |
2025-08-06 14:04:30 +02:00
( ! 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 ;
2025-08-06 00:56:29 +02:00
// Validate: Example info (stars, author, github) missmatches with example header content
2025-08-06 14:04:30 +02:00
rlExampleInfo * exInfoHeader = LoadExampleInfo ( TextFormat ( " %s/%s/%s.c " , exBasePath , exInfo - > category , exInfo - > name ) ) ;
2025-08-06 00:56:29 +02:00
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 ;
2025-08-06 14:04:30 +02:00
UnloadExampleInfo ( exInfoHeader ) ;
}
2025-08-06 00:56:29 +02:00
2025-08-13 23:54:44 +02:00
if ( opCode = = OP_UPDATE )
{
// Actions to fix/review anything possible from validation results
//------------------------------------------------------------------------------------------------
// Check examples "status" information
for ( int i = 0 ; i < exCollectionCount ; i + + )
{
rlExampleInfo * exInfo = & exCollection [ i ] ;
if ( exInfo - > status & VALID_MISSING_C ) LOG ( " WARNING: [%s] Missing code file \n " , exInfo - > name ) ;
else
{
// NOTE: Some issues can not be automatically fixed, only logged
2025-08-14 21:07:36 +02:00
if ( exInfo - > status & VALID_MISSING_PNG ) LOG ( " WARNING: [%s] Missing screenshot file \n " , exInfo - > name ) ;
if ( exInfo - > status & VALID_INVALID_PNG ) LOG ( " WARNING: [%s] Invalid screenshot file (using template) \n " , exInfo - > name ) ;
if ( exInfo - > status & VALID_MISSING_RESOURCES ) LOG ( " WARNING: [%s] Missing resources detected \n " , exInfo - > name ) ;
if ( exInfo - > status & VALID_INCONSISTENT_INFO ) LOG ( " WARNING: [%s] Inconsistent example header info \n " , exInfo - > name ) ;
if ( exInfo - > status & VALID_INVALID_CATEGORY ) LOG ( " WARNING: [%s] Invalid example category \n " , exInfo - > name ) ;
// NOTE: Some examples should be excluded from VS2022 solution because
// they have specific platform/linkage requirements:
if ( ( strcmp ( exInfo - > name , " core_basic_window_web " ) = = 0 ) | |
( strcmp ( exInfo - > name , " core_input_gestures_web " ) = = 0 ) | |
( strcmp ( exInfo - > name , " raylib_opengl_interop " ) = = 0 ) | |
( strcmp ( exInfo - > name , " raymath_vector_angle " ) = = 0 ) ) continue ;
2025-08-13 23:54:44 +02:00
// Review: Add: raylib/projects/VS2022/examples/<category>_example_name.vcxproj
// Review: Add: raylib/projects/VS2022/raylib.sln
// Solves: VALID_MISSING_VCXPROJ, VALID_NOT_IN_VCXSOL
if ( exInfo - > status & VALID_MISSING_VCXPROJ )
{
FileCopy ( TextFormat ( " %s/../projects/VS2022/examples/core_basic_window.vcxproj " , exBasePath ) ,
TextFormat ( " %s/../projects/VS2022/examples/%s.vcxproj " , exBasePath , exInfo - > name ) ) ;
FileTextReplace ( TextFormat ( " %s/../projects/VS2022/examples/%s.vcxproj " , exBasePath , exInfo - > name ) ,
" core_basic_window " , exInfo - > name ) ;
FileTextReplace ( TextFormat ( " %s/../projects/VS2022/examples/%s.vcxproj " , exBasePath , exInfo - > name ) ,
" .. \\ .. \\ examples \\ core " , TextFormat ( " .. \\ .. \\ examples \\ %s " , exInfo - > category ) ) ;
2025-08-14 21:07:36 +02:00
exInfo - > status & = ~ VALID_MISSING_VCXPROJ ;
2025-08-13 23:54:44 +02:00
}
// Add project (.vcxproj) to raylib solution (.sln)
if ( exInfo - > status & VALID_NOT_IN_VCXSOL )
{
AddVSProjectToSolution ( TextFormat ( " %s/../projects/VS2022/examples/%s.vcxproj " , exBasePath , exInfo - > name ) ,
exVSProjectSolutionFile , exInfo - > category ) ;
2025-08-14 21:07:36 +02:00
exInfo - > status & = ~ VALID_NOT_IN_VCXSOL ;
2025-08-13 23:54:44 +02:00
}
// 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
// Solves: VALID_MISSING_WEB_OUTPUT
2025-08-14 21:11:30 +02:00
if ( ( strcmp ( exInfo - > category , " others " ) ! = 0 ) & & // Skipping "others" category
exInfo - > status & VALID_MISSING_WEB_OUTPUT )
2025-08-13 23:54:44 +02:00
{
system ( TextFormat ( " %s/build_example_web.bat %s/%s " , exBasePath , exInfo - > category , exInfo - > name ) ) ;
// Copy results to web side
FileCopy ( TextFormat ( " %s/%s/%s.html " , exBasePath , exInfo - > category , exInfo - > name ) ,
TextFormat ( " %s/%s/%s.html " , exWebPath , exInfo - > category , exInfo - > name ) ) ;
FileCopy ( TextFormat ( " %s/%s/%s.data " , exBasePath , exInfo - > category , exInfo - > name ) ,
TextFormat ( " %s/%s/%s.data " , exWebPath , exInfo - > category , exInfo - > name ) ) ;
FileCopy ( TextFormat ( " %s/%s/%s.wasm " , exBasePath , exInfo - > category , exInfo - > name ) ,
TextFormat ( " %s/%s/%s.wasm " , exWebPath , exInfo - > category , exInfo - > name ) ) ;
FileCopy ( TextFormat ( " %s/%s/%s.js " , exBasePath , exInfo - > category , exInfo - > name ) ,
TextFormat ( " %s/%s/%s.js " , exWebPath , exInfo - > category , exInfo - > name ) ) ;
2025-08-14 21:11:30 +02:00
exInfo - > status & = ~ VALID_MISSING_WEB_OUTPUT ;
2025-08-13 23:54:44 +02:00
}
}
}
// Update files: Makefile, Makefile.Web, README.md, examples.js
// Solves: VALID_NOT_IN_MAKEFILE, VALID_NOT_IN_MAKEFILE_WEB, VALID_NOT_IN_README, VALID_NOT_IN_JS
UpdateRequiredFiles ( ) ;
for ( int i = 0 ; i < exCollectionCount ; i + + )
{
2025-08-14 21:07:36 +02:00
exCollection [ i ] . status & = ~ VALID_NOT_IN_MAKEFILE ;
exCollection [ i ] . status & = ~ VALID_NOT_IN_MAKEFILE_WEB ;
exCollection [ i ] . status & = ~ VALID_NOT_IN_README ;
exCollection [ i ] . status & = ~ VALID_NOT_IN_JS ;
2025-08-13 23:54:44 +02:00
}
//------------------------------------------------------------------------------------------------
}
2025-08-06 14:04:30 +02:00
// Generate validation report/table with results (.md)
2025-08-17 21:32:05 +02:00
//-----------------------------------------------------------------------------------------------------
2025-08-06 14:04:30 +02:00
/*
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
| [ EXAMPLE NAME ] | [ C ] | [ CAT ] | [ INFO ] | [ PNG ] | [ WPNG ] | [ RES ] | [ MK ] | [ MKWEB ] | [ VCX ] | [ SOL ] | [ RDME ] | [ JS ] | [ WOUT ] |
| : - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | : - - - : | : - - - : | : - - - - : | : - - - : | : - - - - : | : - - - : | : - - - : | : - - - - - : | : - - - : | : - - - : | : - - - - : | : - - - : | : - - - - : |
| core_basic_window | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
| shapes_colors_palette | ✘ | ✔ | ✘ | ✔ | ✘ | ✔ | ✔ | ✘ | ✔ | ✔ | ✔ | ✔ | ✔ |
| text_format_text | ✘ | ✘ | ✘ | ✘ | ✘ | ✘ | ✘ | ✘ | ✔ | ✘ | ✔ | ✔ | ✔ |
*/
char * report = ( char * ) RL_CALLOC ( REXM_MAX_BUFFER_SIZE , 1 ) ;
int repIndex = 0 ;
2025-08-06 18:14:52 +02:00
repIndex + = sprintf ( report + repIndex , " # EXAMPLES COLLECTION - VALIDATION REPORT \n \n " ) ;
2025-08-06 15:43:13 +02:00
repIndex + = sprintf ( report + repIndex , " ``` \n Example elements validated: \n \n " ) ;
repIndex + = sprintf ( report + repIndex , " - [C] : Missing .c source file \n " ) ;
repIndex + = sprintf ( report + repIndex , " - [CAT] : Not a recognized category \n " ) ;
repIndex + = sprintf ( report + repIndex , " - [INFO] : Inconsistent example header info (stars, author...) \n " ) ;
repIndex + = sprintf ( report + repIndex , " - [PNG] : Missing screenshot .png \n " ) ;
repIndex + = sprintf ( report + repIndex , " - [WPNG] : Invalid png screenshot (using default one) \n " ) ;
repIndex + = sprintf ( report + repIndex , " - [RES] : Missing resources listed in the code \n " ) ;
repIndex + = sprintf ( report + repIndex , " - [MK] : Not listed in Makefile \n " ) ;
repIndex + = sprintf ( report + repIndex , " - [MKWEB] : Not listed in Makefile.Web \n " ) ;
repIndex + = sprintf ( report + repIndex , " - [VCX] : Missing Visual Studio project file \n " ) ;
repIndex + = sprintf ( report + repIndex , " - [SOL] : Project not included in solution file \n " ) ;
repIndex + = sprintf ( report + repIndex , " - [RDME] : Not listed in README.md \n " ) ;
repIndex + = sprintf ( report + repIndex , " - [JS] : Not listed in Web (examples.js) \n " ) ;
repIndex + = sprintf ( report + repIndex , " - [WOUT] : Missing Web build (.html/.data/.wasm/.js) \n ``` \n " ) ;
2025-08-06 14:04:30 +02:00
repIndex + = sprintf ( report + repIndex , " | **EXAMPLE NAME** | [C] | [CAT]| [INFO]|[PNG]|[WPNG]| [RES]| [MK] |[MKWEB]| [VCX]| [SOL]|[RDME]|[JS] | [WOUT]| \n " ) ;
repIndex + = sprintf ( report + repIndex , " |:---------------------------------|:---:|:----:|:-----:|:---:|:----:|:----:|:----:|:-----:|:----:|:----:|:----:|:---:|:-----:| \n " ) ;
for ( int i = 0 ; i < exCollectionCount ; i + + )
{
repIndex + = sprintf ( report + repIndex , " | %-32s | %s | %s | %s | %s | %s | %s | %s | %s | %s | %s | %s | %s | %s | \n " ,
exCollection [ i ] . name ,
( exCollection [ i ] . status & VALID_MISSING_C ) ? " ❌ " : " ✔ " ,
( exCollection [ i ] . status & VALID_INVALID_CATEGORY ) ? " ❌ " : " ✔ " ,
( exCollection [ i ] . status & VALID_INCONSISTENT_INFO ) ? " ❌ " : " ✔ " ,
( exCollection [ i ] . status & VALID_MISSING_PNG ) ? " ❌ " : " ✔ " ,
( exCollection [ i ] . status & VALID_INVALID_PNG ) ? " ❌ " : " ✔ " ,
( exCollection [ i ] . status & VALID_MISSING_RESOURCES ) ? " ❌ " : " ✔ " ,
( exCollection [ i ] . status & VALID_NOT_IN_MAKEFILE ) ? " ❌ " : " ✔ " ,
( exCollection [ i ] . status & VALID_NOT_IN_MAKEFILE_WEB ) ? " ❌ " : " ✔ " ,
( exCollection [ i ] . status & VALID_MISSING_VCXPROJ ) ? " ❌ " : " ✔ " ,
( exCollection [ i ] . status & VALID_NOT_IN_VCXSOL ) ? " ❌ " : " ✔ " ,
( exCollection [ i ] . status & VALID_NOT_IN_README ) ? " ❌ " : " ✔ " ,
( exCollection [ i ] . status & VALID_NOT_IN_JS ) ? " ❌ " : " ✔ " ,
( exCollection [ i ] . status & VALID_MISSING_WEB_OUTPUT ) ? " ❌ " : " ✔ " ) ;
2025-08-06 00:56:29 +02:00
}
2025-08-06 14:04:30 +02:00
SaveFileText ( TextFormat ( " %s/../tools/rexm/%s " , exBasePath , " examples_report.md " ) , report ) ;
RL_FREE ( report ) ;
2025-08-17 21:32:05 +02:00
//-----------------------------------------------------------------------------------------------------
// Generate a report with only the examples missing some elements
//-----------------------------------------------------------------------------------------------------
char * reportIssues = ( char * ) RL_CALLOC ( REXM_MAX_BUFFER_SIZE , 1 ) ;
repIndex = 0 ;
repIndex + = sprintf ( reportIssues + repIndex , " # EXAMPLES COLLECTION - VALIDATION REPORT \n \n " ) ;
repIndex + = sprintf ( reportIssues + repIndex , " ``` \n Example elements validated: \n \n " ) ;
repIndex + = sprintf ( reportIssues + repIndex , " - [C] : Missing .c source file \n " ) ;
repIndex + = sprintf ( reportIssues + repIndex , " - [CAT] : Not a recognized category \n " ) ;
repIndex + = sprintf ( reportIssues + repIndex , " - [INFO] : Inconsistent example header info (stars, author...) \n " ) ;
repIndex + = sprintf ( reportIssues + repIndex , " - [PNG] : Missing screenshot .png \n " ) ;
repIndex + = sprintf ( reportIssues + repIndex , " - [WPNG] : Invalid png screenshot (using default one) \n " ) ;
repIndex + = sprintf ( reportIssues + repIndex , " - [RES] : Missing resources listed in the code \n " ) ;
repIndex + = sprintf ( reportIssues + repIndex , " - [MK] : Not listed in Makefile \n " ) ;
repIndex + = sprintf ( reportIssues + repIndex , " - [MKWEB] : Not listed in Makefile.Web \n " ) ;
repIndex + = sprintf ( reportIssues + repIndex , " - [VCX] : Missing Visual Studio project file \n " ) ;
repIndex + = sprintf ( reportIssues + repIndex , " - [SOL] : Project not included in solution file \n " ) ;
repIndex + = sprintf ( reportIssues + repIndex , " - [RDME] : Not listed in README.md \n " ) ;
repIndex + = sprintf ( reportIssues + repIndex , " - [JS] : Not listed in Web (examples.js) \n " ) ;
repIndex + = sprintf ( reportIssues + repIndex , " - [WOUT] : Missing Web build (.html/.data/.wasm/.js) \n ``` \n " ) ;
repIndex + = sprintf ( reportIssues + repIndex , " | **EXAMPLE NAME** | [C] | [CAT]| [INFO]|[PNG]|[WPNG]| [RES]| [MK] |[MKWEB]| [VCX]| [SOL]|[RDME]|[JS] | [WOUT]| \n " ) ;
repIndex + = sprintf ( reportIssues + repIndex , " |:---------------------------------|:---:|:----:|:-----:|:---:|:----:|:----:|:----:|:-----:|:----:|:----:|:----:|:---:|:-----:| \n " ) ;
for ( int i = 0 ; i < exCollectionCount ; i + + )
{
if ( exCollection [ i ] . status > 0 )
{
repIndex + = sprintf ( reportIssues + repIndex , " | %-32s | %s | %s | %s | %s | %s | %s | %s | %s | %s | %s | %s | %s | %s | \n " ,
exCollection [ i ] . name ,
( exCollection [ i ] . status & VALID_MISSING_C ) ? " ❌ " : " ✔ " ,
( exCollection [ i ] . status & VALID_INVALID_CATEGORY ) ? " ❌ " : " ✔ " ,
( exCollection [ i ] . status & VALID_INCONSISTENT_INFO ) ? " ❌ " : " ✔ " ,
( exCollection [ i ] . status & VALID_MISSING_PNG ) ? " ❌ " : " ✔ " ,
( exCollection [ i ] . status & VALID_INVALID_PNG ) ? " ❌ " : " ✔ " ,
( exCollection [ i ] . status & VALID_MISSING_RESOURCES ) ? " ❌ " : " ✔ " ,
( exCollection [ i ] . status & VALID_NOT_IN_MAKEFILE ) ? " ❌ " : " ✔ " ,
( exCollection [ i ] . status & VALID_NOT_IN_MAKEFILE_WEB ) ? " ❌ " : " ✔ " ,
( exCollection [ i ] . status & VALID_MISSING_VCXPROJ ) ? " ❌ " : " ✔ " ,
( exCollection [ i ] . status & VALID_NOT_IN_VCXSOL ) ? " ❌ " : " ✔ " ,
( exCollection [ i ] . status & VALID_NOT_IN_README ) ? " ❌ " : " ✔ " ,
( exCollection [ i ] . status & VALID_NOT_IN_JS ) ? " ❌ " : " ✔ " ,
( exCollection [ i ] . status & VALID_MISSING_WEB_OUTPUT ) ? " ❌ " : " ✔ " ) ;
}
}
SaveFileText ( TextFormat ( " %s/../tools/rexm/%s " , exBasePath , " examples_report_issues.md " ) , reportIssues ) ;
RL_FREE ( reportIssues ) ;
//-----------------------------------------------------------------------------------------------------
2025-08-06 14:04:30 +02:00
UnloadExamplesData ( exCollection ) ;
//------------------------------------------------------------------------------------------------
2025-08-01 19:40:44 +02:00
2025-08-01 00:30:01 +02:00
} break ;
default : // Help
{
// Supported commands:
// help : Provides command-line usage information
// create <new_example_name> : Creates an empty example, from internal template
// add <example_name> : Add existing example, category extracted from name
// rename <old_examples_name> <new_example_name> : Rename an existing example
// remove <example_name> : Remove an existing example
2025-08-01 19:40:44 +02:00
2025-08-01 00:30:01 +02:00
printf ( " \n //////////////////////////////////////////////////////////////////////////////////////////// \n " ) ;
printf ( " // // \n " ) ;
2025-08-01 01:08:31 +02:00
printf ( " // rexm [raylib examples manager] - A simple command-line tool to manage raylib examples // \n " ) ;
2025-08-01 00:30:01 +02:00
printf ( " // powered by raylib v5.6-dev // \n " ) ;
printf ( " // // \n " ) ;
printf ( " // Copyright (c) 2025 Ramon Santamaria (@raysan5) // \n " ) ;
printf ( " // // \n " ) ;
printf ( " //////////////////////////////////////////////////////////////////////////////////////////// \n \n " ) ;
printf ( " USAGE: \n \n " ) ;
2025-08-01 01:08:31 +02:00
printf ( " > rexm help|create|add|rename|remove <example_name> [<example_rename>] \n " ) ;
2025-08-01 00:30:01 +02:00
printf ( " \n OPTIONS: \n \n " ) ;
printf ( " help : Provides command-line usage information \n " ) ;
printf ( " create <new_example_name> : Creates an empty example, from internal template \n " ) ;
printf ( " add <example_name> : Add existing example, category extracted from name \n " ) ;
2025-08-01 01:08:31 +02:00
printf ( " Supported categories: core, shapes, textures, text, models \n " ) ;
2025-08-01 00:30:01 +02:00
printf ( " rename <old_examples_name> <new_example_name> : Rename an existing example \n " ) ;
printf ( " remove <example_name> : Remove an existing example \n \n " ) ;
printf ( " \n EXAMPLES: \n \n " ) ;
2025-08-01 01:08:31 +02:00
printf ( " > rexm add shapes_custom_stars \n " ) ;
2025-08-01 00:30:01 +02:00
printf ( " Add and updates new example provided <shapes_custom_stars> \n \n " ) ;
2025-08-01 01:08:31 +02:00
printf ( " > rexm rename core_basic_window core_cool_window \n " ) ;
printf ( " Renames and updates example <core_basic_window> to <core_cool_window> \n \n " ) ;
2025-08-01 00:30:01 +02:00
} break ;
}
2025-08-01 19:40:44 +02:00
2025-08-01 00:30:01 +02:00
return 0 ;
}
//----------------------------------------------------------------------------------
// Module specific functions definition
//----------------------------------------------------------------------------------
2025-08-03 22:56:15 +02:00
// Update required files from examples collection
static int UpdateRequiredFiles ( void )
{
int result = 0 ;
// Edit: raylib/examples/Makefile --> Update from collection
//------------------------------------------------------------------------------------------------
char * mkText = LoadFileText ( TextFormat ( " %s/Makefile " , exBasePath ) ) ;
2025-08-13 12:33:38 +02:00
char * mkTextUpdated = ( char * ) RL_CALLOC ( REXM_MAX_BUFFER_SIZE , 1 ) ; // Updated Makefile copy, 2MB
2025-08-03 22:56:15 +02:00
int mkListStartIndex = TextFindIndex ( mkText , " #EXAMPLES_LIST_START " ) ;
int mkListEndIndex = TextFindIndex ( mkText , " #EXAMPLES_LIST_END " ) ;
int mkIndex = 0 ;
memcpy ( mkTextUpdated , mkText , mkListStartIndex ) ;
mkIndex = sprintf ( mkTextUpdated + mkListStartIndex , " #EXAMPLES_LIST_START \n " ) ;
2025-08-05 12:37:11 +02:00
for ( int i = 0 ; i < REXM_MAX_EXAMPLE_CATEGORIES ; i + + )
2025-08-03 22:56:15 +02:00
{
mkIndex + = sprintf ( mkTextUpdated + mkListStartIndex + mkIndex , TextFormat ( " %s = \\ \n " , TextToUpper ( exCategories [ i ] ) ) ) ;
2025-08-04 19:27:48 +02:00
int exCollectionCount = 0 ;
rlExampleInfo * exCollection = LoadExamplesData ( exCollectionFilePath , exCategories [ i ] , true , & exCollectionCount ) ;
2025-08-03 22:56:15 +02:00
2025-08-04 19:27:48 +02:00
for ( int x = 0 ; x < exCollectionCount - 1 ; x + + ) mkIndex + = sprintf ( mkTextUpdated + mkListStartIndex + mkIndex , TextFormat ( " %s/%s \\ \n " , exCollection [ x ] . category , exCollection [ x ] . name ) ) ;
mkIndex + = sprintf ( mkTextUpdated + mkListStartIndex + mkIndex , TextFormat ( " %s/%s \n \n " , exCollection [ exCollectionCount - 1 ] . category , exCollection [ exCollectionCount - 1 ] . name ) ) ;
2025-08-03 22:56:15 +02:00
2025-08-04 19:27:48 +02:00
UnloadExamplesData ( exCollection ) ;
2025-08-03 22:56:15 +02:00
}
// Add the remaining part of the original file
2025-08-03 23:32:54 +02:00
memcpy ( mkTextUpdated + mkListStartIndex + mkIndex - 1 , mkText + mkListEndIndex , strlen ( mkText ) - mkListEndIndex ) ;
2025-08-03 22:56:15 +02:00
// Save updated file
SaveFileText ( TextFormat ( " %s/Makefile " , exBasePath ) , mkTextUpdated ) ;
UnloadFileText ( mkText ) ;
RL_FREE ( mkTextUpdated ) ;
//------------------------------------------------------------------------------------------------
// Edit: raylib/examples/Makefile.Web --> Update from collection
2025-08-04 17:51:48 +02:00
// NOTE: We avoid the "others" category on web building
2025-08-03 22:56:15 +02:00
//------------------------------------------------------------------------------------------------
char * mkwText = LoadFileText ( TextFormat ( " %s/Makefile.Web " , exBasePath ) ) ;
2025-08-13 12:33:38 +02:00
char * mkwTextUpdated = ( char * ) RL_CALLOC ( REXM_MAX_BUFFER_SIZE , 1 ) ; // Updated Makefile copy, 2MB
2025-08-03 22:56:15 +02:00
int mkwListStartIndex = TextFindIndex ( mkwText , " #EXAMPLES_LIST_START " ) ;
int mkwListEndIndex = TextFindIndex ( mkwText , " #EXAMPLES_LIST_END " ) ;
int mkwIndex = 0 ;
memcpy ( mkwTextUpdated , mkwText , mkwListStartIndex ) ;
mkwIndex = sprintf ( mkwTextUpdated + mkwListStartIndex , " #EXAMPLES_LIST_START \n " ) ;
2025-08-04 17:51:48 +02:00
// NOTE: We avoid the "others" category on web building
2025-08-05 12:37:11 +02:00
for ( int i = 0 ; i < REXM_MAX_EXAMPLE_CATEGORIES - 1 ; i + + )
2025-08-03 22:56:15 +02:00
{
mkwIndex + = sprintf ( mkwTextUpdated + mkwListStartIndex + mkwIndex , TextFormat ( " %s = \\ \n " , TextToUpper ( exCategories [ i ] ) ) ) ;
2025-08-04 19:27:48 +02:00
int exCollectionCount = 0 ;
rlExampleInfo * exCollection = LoadExamplesData ( exCollectionFilePath , exCategories [ i ] , true , & exCollectionCount ) ;
2025-08-03 22:56:15 +02:00
2025-08-04 19:27:48 +02:00
for ( int x = 0 ; x < exCollectionCount - 1 ; x + + ) mkwIndex + = sprintf ( mkwTextUpdated + mkwListStartIndex + mkwIndex , TextFormat ( " %s/%s \\ \n " , exCollection [ x ] . category , exCollection [ x ] . name ) ) ;
mkwIndex + = sprintf ( mkwTextUpdated + mkwListStartIndex + mkwIndex , TextFormat ( " %s/%s \n \n " , exCollection [ exCollectionCount - 1 ] . category , exCollection [ exCollectionCount - 1 ] . name ) ) ;
2025-08-03 22:56:15 +02:00
2025-08-04 19:27:48 +02:00
UnloadExamplesData ( exCollection ) ;
2025-08-03 22:56:15 +02:00
}
2025-08-04 17:52:22 +02:00
// Add examples individual targets, considering every example resources
// Some required makefile code...
mkwIndex + = sprintf ( mkwTextUpdated + mkwListStartIndex + mkwIndex , " # Default target entry \n " ) ;
mkwIndex + = sprintf ( mkwTextUpdated + mkwListStartIndex + mkwIndex , " all: $(CORE) $(SHAPES) $(TEXT) $(TEXTURES) $(MODELS) $(SHADERS) $(AUDIO) \n \n " ) ;
mkwIndex + = sprintf ( mkwTextUpdated + mkwListStartIndex + mkwIndex , " core: $(CORE) \n " ) ;
mkwIndex + = sprintf ( mkwTextUpdated + mkwListStartIndex + mkwIndex , " shapes: $(SHAPES) \n " ) ;
mkwIndex + = sprintf ( mkwTextUpdated + mkwListStartIndex + mkwIndex , " textures: $(TEXTURES) \n " ) ;
mkwIndex + = sprintf ( mkwTextUpdated + mkwListStartIndex + mkwIndex , " text: $(TEXT) \n " ) ;
mkwIndex + = sprintf ( mkwTextUpdated + mkwListStartIndex + mkwIndex , " models: $(MODELS) \n " ) ;
mkwIndex + = sprintf ( mkwTextUpdated + mkwListStartIndex + mkwIndex , " shaders: $(SHADERS) \n " ) ;
mkwIndex + = sprintf ( mkwTextUpdated + mkwListStartIndex + mkwIndex , " audio: $(AUDIO) \n \n " ) ;
// NOTE: We avoid the "others" category on web building
2025-08-05 12:37:11 +02:00
for ( int i = 0 ; i < REXM_MAX_EXAMPLE_CATEGORIES - 1 ; i + + )
2025-08-04 17:52:22 +02:00
{
mkwIndex + = sprintf ( mkwTextUpdated + mkwListStartIndex + mkwIndex , TextFormat ( " # Compile %s examples \n " , TextToUpper ( exCategories [ i ] ) ) ) ;
2025-08-04 19:27:48 +02:00
int exCollectionCount = 0 ;
rlExampleInfo * exCollection = LoadExamplesData ( exCollectionFilePath , exCategories [ i ] , true , & exCollectionCount ) ;
2025-08-04 17:52:22 +02:00
2025-08-04 19:27:48 +02:00
for ( int x = 0 ; x < exCollectionCount ; x + + )
2025-08-04 17:52:22 +02:00
{
// Scan resources used in example to list
int resPathCount = 0 ;
2025-08-04 19:27:48 +02:00
char * * resPaths = ScanExampleResources ( TextFormat ( " %s/%s/%s.c " , exBasePath , exCollection [ x ] . category , exCollection [ x ] . name ) , & resPathCount ) ;
2025-08-04 17:52:22 +02:00
if ( resPathCount > 0 )
{
/*
// WARNING: Compilation line starts with [TAB]
shaders / shaders_vertex_displacement : shaders / shaders_vertex_displacement . c
$ ( CC ) - o $ @ $ ( EXT ) $ < $ ( CFLAGS ) $ ( INCLUDE_PATHS ) $ ( LDFLAGS ) $ ( LDLIBS ) - D $ ( PLATFORM ) \
- - preload - file shaders / resources / shaders / glsl100 / vertex_displacement . vs @ resources / shaders / glsl100 / vertex_displacement . vs \
- - preload - file shaders / resources / shaders / glsl330 / vertex_displacement . vs @ resources / shaders / glsl330 / vertex_displacement . vs \
- - preload - file shaders / resources / shaders / glsl100 / vertex_displacement . fs @ resources / shaders / glsl100 / vertex_displacement . fs \
- - preload - file shaders / resources / shaders / glsl330 / vertex_displacement . fs @ resources / shaders / glsl330 / vertex_displacement . fs
*/
mkwIndex + = sprintf ( mkwTextUpdated + mkwListStartIndex + mkwIndex ,
2025-08-04 19:27:48 +02:00
TextFormat ( " %s/%s: %s/%s.c \n " , exCollection [ x ] . category , exCollection [ x ] . name , exCollection [ x ] . category , exCollection [ x ] . name ) ) ;
2025-08-04 17:52:22 +02:00
mkwIndex + = sprintf ( mkwTextUpdated + mkwListStartIndex + mkwIndex , " $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \\ \n " ) ;
for ( int r = 0 ; r < resPathCount ; r + + )
{
// WARNING: Special case to consider: shaders, resource paths could use conditions: "glsl%i"
// In this case, we focus on web building for: glsl100
if ( TextFindIndex ( resPaths [ r ] , " glsl%i " ) > - 1 )
{
char * resPathUpdated = TextReplace ( resPaths [ r ] , " glsl%i " , " glsl100 " ) ;
memset ( resPaths [ r ] , 0 , 256 ) ;
strcpy ( resPaths [ r ] , resPathUpdated ) ;
RL_FREE ( resPathUpdated ) ;
}
if ( r < ( resPathCount - 1 ) )
{
mkwIndex + = sprintf ( mkwTextUpdated + mkwListStartIndex + mkwIndex ,
2025-08-04 19:27:48 +02:00
TextFormat ( " --preload-file %s/%s@%s \\ \n " , exCollection [ x ] . category , resPaths [ r ] , resPaths [ r ] ) ) ;
2025-08-04 17:52:22 +02:00
}
else
{
mkwIndex + = sprintf ( mkwTextUpdated + mkwListStartIndex + mkwIndex ,
2025-08-04 19:27:48 +02:00
TextFormat ( " --preload-file %s/%s@%s \n \n " , exCollection [ x ] . category , resPaths [ r ] , resPaths [ r ] ) ) ;
2025-08-04 17:52:22 +02:00
}
}
}
else // Example does not require resources
{
/*
// WARNING: Compilation line starts with [TAB]
core / core_2d_camera : core / core_2d_camera . c
$ ( CC ) - o $ @ $ ( EXT ) $ < $ ( CFLAGS ) $ ( INCLUDE_PATHS ) $ ( LDFLAGS ) $ ( LDLIBS ) - D $ ( PLATFORM )
*/
mkwIndex + = sprintf ( mkwTextUpdated + mkwListStartIndex + mkwIndex ,
2025-08-04 19:27:48 +02:00
TextFormat ( " %s/%s: %s/%s.c \n " , exCollection [ x ] . category , exCollection [ x ] . name , exCollection [ x ] . category , exCollection [ x ] . name ) ) ;
2025-08-04 17:52:22 +02:00
mkwIndex + = sprintf ( mkwTextUpdated + mkwListStartIndex + mkwIndex , " $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \n \n " ) ;
}
ClearExampleResources ( resPaths ) ;
}
2025-08-04 19:27:48 +02:00
UnloadExamplesData ( exCollection ) ;
2025-08-04 17:52:22 +02:00
}
2025-08-03 22:56:15 +02:00
// Add the remaining part of the original file
2025-08-03 23:32:54 +02:00
memcpy ( mkwTextUpdated + mkwListStartIndex + mkwIndex - 1 , mkwText + mkwListEndIndex , strlen ( mkwText ) - mkwListEndIndex ) ;
2025-08-03 22:56:15 +02:00
// Save updated file
SaveFileText ( TextFormat ( " %s/Makefile.Web " , exBasePath ) , mkwTextUpdated ) ;
UnloadFileText ( mkwText ) ;
RL_FREE ( mkwTextUpdated ) ;
//------------------------------------------------------------------------------------------------
// Edit: raylib/examples/README.md --> Update from collection
//------------------------------------------------------------------------------------------------
// NOTE: Using [examples_list.txt] to update/regen README.md
// Lines format: | 01 | [core_basic_window](core/core_basic_window.c) | <img src="core/core_basic_window.png" alt="core_basic_window" width="80"> | ⭐️☆☆☆ | 1.0 | 1.0 | [Ray](https://github.com/raysan5) |
char * mdText = LoadFileText ( TextFormat ( " %s/README.md " , exBasePath ) ) ;
2025-08-13 12:33:38 +02:00
char * mdTextUpdated = ( char * ) RL_CALLOC ( REXM_MAX_BUFFER_SIZE , 1 ) ; // Updated examples.js copy, 2MB
2025-08-03 22:56:15 +02:00
2025-08-04 19:27:48 +02:00
int mdListStartIndex = TextFindIndex ( mdText , " ## EXAMPLES COLLECTION " ) ;
2025-08-03 22:56:15 +02:00
int mdIndex = 0 ;
memcpy ( mdTextUpdated , mdText , mdListStartIndex ) ;
2025-08-04 19:27:48 +02:00
int exCollectionFullCount = 0 ;
rlExampleInfo * exCollectionFull = LoadExamplesData ( exCollectionFilePath , " ALL " , false , & exCollectionFullCount ) ;
UnloadExamplesData ( exCollectionFull ) ;
2025-08-06 18:25:58 +02:00
mdIndex + = sprintf ( mdTextUpdated + mdListStartIndex + mdIndex , TextFormat ( " ## EXAMPLES COLLECTION [TOTAL: %i] \n " , exCollectionFullCount ) ) ;
2025-08-04 19:27:48 +02:00
2025-08-03 22:56:15 +02:00
// NOTE: We keep a global examples counter
2025-08-05 12:37:11 +02:00
for ( int i = 0 ; i < REXM_MAX_EXAMPLE_CATEGORIES ; i + + )
2025-08-03 22:56:15 +02:00
{
2025-08-04 19:27:48 +02:00
int exCollectionCount = 0 ;
rlExampleInfo * exCollection = LoadExamplesData ( exCollectionFilePath , exCategories [ i ] , false , & exCollectionCount ) ;
2025-08-03 22:56:15 +02:00
// Every category includes some introductory text, as it is quite short, just copying it here
2025-08-04 19:27:48 +02:00
if ( i = = 0 ) // "core"
2025-08-03 22:56:15 +02:00
{
2025-08-04 19:27:48 +02:00
mdIndex + = sprintf ( mdTextUpdated + mdListStartIndex + mdIndex , TextFormat ( " \n ### category: core [%i] \n \n " , exCollectionCount ) ) ;
mdIndex + = sprintf ( mdTextUpdated + mdListStartIndex + mdIndex ,
" Examples using raylib[core](../src/rcore.c) platform functionality like window creation, inputs, drawing modes and system functionality. \n \n " ) ;
}
else if ( i = = 1 ) // "shapes"
{
mdIndex + = sprintf ( mdTextUpdated + mdListStartIndex + mdIndex , TextFormat ( " \n ### category: shapes [%i] \n \n " , exCollectionCount ) ) ;
2025-08-03 22:56:15 +02:00
mdIndex + = sprintf ( mdTextUpdated + mdListStartIndex + mdIndex ,
" Examples using raylib shapes drawing functionality, provided by raylib [shapes](../src/rshapes.c) module. \n \n " ) ;
}
else if ( i = = 2 ) // "textures"
{
2025-08-04 19:27:48 +02:00
mdIndex + = sprintf ( mdTextUpdated + mdListStartIndex + mdIndex , TextFormat ( " \n ### category: textures [%i] \n \n " , exCollectionCount ) ) ;
2025-08-03 22:56:15 +02:00
mdIndex + = sprintf ( mdTextUpdated + mdListStartIndex + mdIndex ,
" Examples using raylib textures functionality, including image/textures loading/generation and drawing, provided by raylib [textures](../src/rtextures.c) module. \n \n " ) ;
}
else if ( i = = 3 ) // "text"
{
2025-08-04 19:27:48 +02:00
mdIndex + = sprintf ( mdTextUpdated + mdListStartIndex + mdIndex , TextFormat ( " \n ### category: text [%i] \n \n " , exCollectionCount ) ) ;
2025-08-03 22:56:15 +02:00
mdIndex + = sprintf ( mdTextUpdated + mdListStartIndex + mdIndex ,
" Examples using raylib text functionality, including sprite fonts loading/generation and text drawing, provided by raylib [text](../src/rtext.c) module. \n \n " ) ;
}
else if ( i = = 4 ) // "models"
{
2025-08-04 19:27:48 +02:00
mdIndex + = sprintf ( mdTextUpdated + mdListStartIndex + mdIndex , TextFormat ( " \n ### category: models [%i] \n \n " , exCollectionCount ) ) ;
2025-08-03 22:56:15 +02:00
mdIndex + = sprintf ( mdTextUpdated + mdListStartIndex + mdIndex ,
" Examples using raylib models functionality, including models loading/generation and drawing, provided by raylib [models](../src/rmodels.c) module. \n \n " ) ;
}
else if ( i = = 5 ) // "shaders"
{
2025-08-04 19:27:48 +02:00
mdIndex + = sprintf ( mdTextUpdated + mdListStartIndex + mdIndex , TextFormat ( " \n ### category: shaders [%i] \n \n " , exCollectionCount ) ) ;
2025-08-03 22:56:15 +02:00
mdIndex + = sprintf ( mdTextUpdated + mdListStartIndex + mdIndex ,
" Examples using raylib shaders functionality, including shaders loading, parameters configuration and drawing using them (model shaders and postprocessing shaders). This functionality is directly provided by raylib [rlgl](../src/rlgl.c) module. \n \n " ) ;
}
else if ( i = = 6 ) // "audio"
{
2025-08-04 19:27:48 +02:00
mdIndex + = sprintf ( mdTextUpdated + mdListStartIndex + mdIndex , TextFormat ( " \n ### category: audio [%i] \n \n " , exCollectionCount ) ) ;
2025-08-03 22:56:15 +02:00
mdIndex + = sprintf ( mdTextUpdated + mdListStartIndex + mdIndex ,
" Examples using raylib audio functionality, including sound/music loading and playing. This functionality is provided by raylib [raudio](../src/raudio.c) module. Note this module can be used standalone independently of raylib. \n \n " ) ;
}
else if ( i = = 7 ) // "others"
{
2025-08-04 19:27:48 +02:00
mdIndex + = sprintf ( mdTextUpdated + mdListStartIndex + mdIndex , TextFormat ( " \n ### category: others [%i] \n \n " , exCollectionCount ) ) ;
2025-08-03 22:56:15 +02:00
mdIndex + = sprintf ( mdTextUpdated + mdListStartIndex + mdIndex ,
" Examples showing raylib misc functionality that does not fit in other categories, like standalone modules usage or examples integrating external libraries. \n \n " ) ;
}
2025-08-04 19:27:48 +02:00
// Table header required
mdIndex + = sprintf ( mdTextUpdated + mdListStartIndex + mdIndex , " | example | image | difficulty<br>level | version<br>created | last version<br>updated | original<br>developer | \n " ) ;
mdIndex + = sprintf ( mdTextUpdated + mdListStartIndex + mdIndex , " |-----------|--------|:-------------------:|:------------------:|:-----------------------:|:----------------------| \n " ) ;
2025-08-03 22:56:15 +02:00
2025-08-04 19:27:48 +02:00
for ( int x = 0 ; x < exCollectionCount ; x + + )
2025-08-03 22:56:15 +02:00
{
char stars [ 16 ] = { 0 } ;
for ( int s = 0 ; s < 4 ; s + + )
{
2025-08-04 19:27:48 +02:00
if ( s < exCollection [ x ] . stars ) strcpy ( stars + 3 * s , " ⭐️ " ) ;
2025-08-03 22:56:15 +02:00
else strcpy ( stars + 3 * s , " ☆ " ) ;
}
mdIndex + = sprintf ( mdTextUpdated + mdListStartIndex + mdIndex ,
2025-08-04 19:27:48 +02:00
TextFormat ( " | [%s](%s/%s.c) | <img src= \" %s/%s.png \" alt= \" %s \" width= \" 80 \" > | %s | %.1f | %.1f | [%s](https://github.com/%s) | \n " ,
exCollection [ x ] . name , exCollection [ x ] . category , exCollection [ x ] . name , exCollection [ x ] . category , exCollection [ x ] . name , exCollection [ x ] . name ,
2025-08-06 18:20:34 +02:00
stars , exCollection [ x ] . verCreated , exCollection [ x ] . verUpdated , exCollection [ x ] . author , exCollection [ x ] . authorGitHub ) ) ;
2025-08-03 22:56:15 +02:00
}
2025-08-04 19:27:48 +02:00
UnloadExamplesData ( exCollection ) ;
2025-08-03 22:56:15 +02:00
}
2025-08-04 19:27:48 +02:00
mdIndex + = sprintf ( mdTextUpdated + mdListStartIndex + mdIndex ,
" \n Some example missing? As always, contributions are welcome, feel free to send new examples! \n " ) ;
mdIndex + = sprintf ( mdTextUpdated + mdListStartIndex + mdIndex ,
" Here is an[examples template](examples_template.c) with instructions to start with! \n " ) ;
2025-08-03 22:56:15 +02:00
// Save updated file
SaveFileText ( TextFormat ( " %s/README.md " , exBasePath ) , mdTextUpdated ) ;
UnloadFileText ( mdText ) ;
RL_FREE ( mdTextUpdated ) ;
//------------------------------------------------------------------------------------------------
// Edit: raylib.com/common/examples.js --> Update from collection
// NOTE: Entries format: exampleEntry('⭐️☆☆☆' , 'core' , 'basic_window'),
//------------------------------------------------------------------------------------------------
char * jsText = LoadFileText ( TextFormat ( " %s/../common/examples.js " , exWebPath ) ) ;
2025-08-13 12:33:38 +02:00
char * jsTextUpdated = ( char * ) RL_CALLOC ( REXM_MAX_BUFFER_SIZE , 1 ) ; // Updated examples.js copy, 2MB
2025-08-03 22:56:15 +02:00
int jsListStartIndex = TextFindIndex ( jsText , " //EXAMPLE_DATA_LIST_START " ) ;
int jsListEndIndex = TextFindIndex ( jsText , " //EXAMPLE_DATA_LIST_END " ) ;
int jsIndex = 0 ;
memcpy ( jsTextUpdated , jsText , jsListStartIndex ) ;
jsIndex = sprintf ( jsTextUpdated + jsListStartIndex , " //EXAMPLE_DATA_LIST_START \n " ) ;
jsIndex + = sprintf ( jsTextUpdated + jsListStartIndex + jsIndex , " var exampleData = [ \n " ) ;
// NOTE: We avoid "others" category
2025-08-05 12:37:11 +02:00
for ( int i = 0 ; i < REXM_MAX_EXAMPLE_CATEGORIES - 1 ; i + + )
2025-08-03 22:56:15 +02:00
{
2025-08-04 19:27:48 +02:00
int exCollectionCount = 0 ;
rlExampleInfo * exCollection = LoadExamplesData ( exCollectionFilePath , exCategories [ i ] , false , & exCollectionCount ) ;
for ( int x = 0 ; x < exCollectionCount ; x + + )
2025-08-03 22:56:15 +02:00
{
char stars [ 16 ] = { 0 } ;
for ( int s = 0 ; s < 4 ; s + + )
{
2025-08-04 19:27:48 +02:00
if ( s < exCollection [ x ] . stars ) strcpy ( stars + 3 * s , " ⭐️ " ) ;
2025-08-03 22:56:15 +02:00
else strcpy ( stars + 3 * s , " ☆ " ) ;
}
2025-08-04 19:27:48 +02:00
if ( ( i = = 6 ) & & ( x = = ( exCollectionCount - 1 ) ) )
2025-08-03 22:56:15 +02:00
{
// NOTE: Last line to add, special case to consider
jsIndex + = sprintf ( jsTextUpdated + jsListStartIndex + jsIndex ,
2025-08-04 19:27:48 +02:00
TextFormat ( " exampleEntry('%s', '%s', '%s')]; \n " , stars , exCollection [ x ] . category , exCollection [ x ] . name + strlen ( exCollection [ x ] . category ) + 1 ) ) ;
2025-08-03 22:56:15 +02:00
}
else
{
jsIndex + = sprintf ( jsTextUpdated + jsListStartIndex + jsIndex ,
2025-08-04 19:27:48 +02:00
TextFormat ( " exampleEntry('%s', '%s', '%s'), \n " , stars , exCollection [ x ] . category , exCollection [ x ] . name + strlen ( exCollection [ x ] . category ) + 1 ) ) ;
2025-08-03 22:56:15 +02:00
}
}
2025-08-04 19:27:48 +02:00
UnloadExamplesData ( exCollection ) ;
2025-08-03 22:56:15 +02:00
}
// Add the remaining part of the original file
memcpy ( jsTextUpdated + jsListStartIndex + jsIndex , jsText + jsListEndIndex , strlen ( jsText ) - jsListEndIndex ) ;
// Save updated file
SaveFileText ( TextFormat ( " %s/../common/examples.js " , exWebPath ) , jsTextUpdated ) ;
UnloadFileText ( jsText ) ;
RL_FREE ( jsTextUpdated ) ;
//------------------------------------------------------------------------------------------------
return result ;
}
2025-08-01 19:40:44 +02:00
// Load examples collection information
2025-08-02 18:00:41 +02:00
static rlExampleInfo * LoadExamplesData ( const char * fileName , const char * category , bool sort , int * exCount )
2025-08-01 19:40:44 +02:00
{
# define MAX_EXAMPLES_INFO 256
rlExampleInfo * exInfo = ( rlExampleInfo * ) RL_CALLOC ( MAX_EXAMPLES_INFO , sizeof ( rlExampleInfo ) ) ;
2025-08-02 18:00:41 +02:00
int exCounter = 0 ;
2025-08-02 19:21:36 +02:00
* exCount = 0 ;
2025-08-01 19:40:44 +02:00
2025-08-02 18:00:41 +02:00
char * text = LoadFileText ( fileName ) ;
2025-08-01 19:40:44 +02:00
if ( text ! = NULL )
{
int lineCount = 0 ;
2025-08-04 20:44:22 +02:00
char * * lines = LoadTextLines ( text , & lineCount ) ;
2025-08-01 19:40:44 +02:00
for ( int i = 0 ; i < lineCount ; i + + )
{
// Basic validation for lines start categories
2025-08-03 01:17:50 +02:00
if ( ( lines [ i ] [ 0 ] ! = ' # ' ) & &
( ( lines [ i ] [ 0 ] = = ' c ' ) | | // core
( lines [ i ] [ 0 ] = = ' s ' ) | | // shapes, shaders
( lines [ i ] [ 0 ] = = ' t ' ) | | // textures, text
( lines [ i ] [ 0 ] = = ' m ' ) | | // models
( lines [ i ] [ 0 ] = = ' a ' ) | | // audio
( lines [ i ] [ 0 ] = = ' o ' ) ) ) // others
2025-08-01 19:40:44 +02:00
{
2025-08-02 18:00:41 +02:00
rlExampleInfo info = { 0 } ;
2025-08-03 01:17:50 +02:00
int result = ParseExampleInfoLine ( lines [ i ] , & info ) ;
2025-08-02 18:00:41 +02:00
if ( result = = 1 ) // Success on parsing
{
if ( strcmp ( category , " ALL " ) = = 0 )
{
// Add all examples to the list
memcpy ( & exInfo [ exCounter ] , & info , sizeof ( rlExampleInfo ) ) ;
exCounter + + ;
}
else if ( strcmp ( info . category , category ) = = 0 )
{
// Get only specific category examples
memcpy ( & exInfo [ exCounter ] , & info , sizeof ( rlExampleInfo ) ) ;
exCounter + + ;
}
}
2025-08-01 19:40:44 +02:00
}
}
2025-08-02 18:00:41 +02:00
2025-08-04 20:44:22 +02:00
UnloadTextLines ( lines ) ;
2025-08-02 18:00:41 +02:00
UnloadFileText ( text ) ;
2025-08-01 19:40:44 +02:00
}
2025-08-02 18:00:41 +02:00
// Sorting required
if ( sort ) SortExampleByName ( exInfo , exCounter ) ;
* exCount = exCounter ;
2025-08-01 19:40:44 +02:00
return exInfo ;
}
// Unload examples collection data
static void UnloadExamplesData ( rlExampleInfo * exInfo )
{
RL_FREE ( exInfo ) ;
}
2025-08-06 00:56:29 +02:00
// 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 ;
}
2025-08-01 19:40:44 +02:00
// Replace text in an existing file
2025-08-01 00:30:01 +02:00
static int FileTextReplace ( const char * fileName , const char * textLookUp , const char * textReplace )
{
int result = 0 ;
char * fileText = NULL ;
char * fileTextUpdated = { 0 } ;
2025-08-01 19:40:44 +02:00
if ( FileExists ( fileName ) )
{
fileText = LoadFileText ( fileName ) ;
fileTextUpdated = TextReplace ( fileText , textLookUp , textReplace ) ;
result = SaveFileText ( fileName , fileTextUpdated ) ;
MemFree ( fileTextUpdated ) ;
UnloadFileText ( fileText ) ;
}
2025-08-01 00:30:01 +02:00
return result ;
}
2025-08-01 19:40:44 +02:00
// Copy file from one path to another
// WARNING: Destination path must exist
2025-08-01 00:30:01 +02:00
static int FileCopy ( const char * srcPath , const char * dstPath )
{
int result = 0 ;
int srcDataSize = 0 ;
unsigned char * srcFileData = LoadFileData ( srcPath , & srcDataSize ) ;
2025-08-03 23:49:52 +02:00
// Create required paths if they do not exist
if ( ! DirectoryExists ( GetDirectoryPath ( dstPath ) ) )
MakeDirectory ( GetDirectoryPath ( dstPath ) ) ;
2025-08-01 19:40:44 +02:00
2025-08-03 23:49:52 +02:00
if ( ( srcFileData ! = NULL ) & & ( srcDataSize > 0 ) )
result = SaveFileData ( dstPath , srcFileData , srcDataSize ) ;
2025-08-01 00:30:01 +02:00
UnloadFileData ( srcFileData ) ;
2025-08-01 19:40:44 +02:00
2025-08-01 00:30:01 +02:00
return result ;
}
2025-08-01 19:40:44 +02:00
// Rename file (if exists)
// NOTE: Only rename file name required, not full path
2025-08-01 00:30:01 +02:00
static int FileRename ( const char * fileName , const char * fileRename )
{
int result = 0 ;
2025-08-01 19:40:44 +02:00
2025-08-21 19:37:59 +02:00
if ( FileExists ( fileName ) )
{
result = rename ( fileName , TextFormat ( " %s/%s " , GetDirectoryPath ( fileName ) , fileRename ) ) ;
}
else result = - 1 ;
2025-08-01 00:30:01 +02:00
return result ;
}
2025-08-01 19:40:44 +02:00
// Remove file (if exists)
2025-08-01 00:30:01 +02:00
static int FileRemove ( const char * fileName )
{
int result = 0 ;
2025-08-01 19:40:44 +02:00
2025-08-21 19:37:59 +02:00
if ( FileExists ( fileName ) )
{
result = remove ( fileName ) ;
}
else result = - 1 ;
2025-08-01 00:30:01 +02:00
return result ;
}
2025-08-01 19:40:44 +02:00
2025-08-04 18:11:57 +02:00
// Move file from one directory to another
2025-08-04 19:27:48 +02:00
// NOTE: If dst directories do not exists they are created
2025-08-04 18:11:57 +02:00
static int FileMove ( const char * srcPath , const char * dstPath )
{
int result = 0 ;
if ( FileExists ( srcPath ) )
{
FileCopy ( srcPath , dstPath ) ;
remove ( srcPath ) ;
}
2025-08-21 19:37:59 +02:00
else result = - 1 ;
2025-08-04 18:11:57 +02:00
return result ;
}
2025-08-03 01:17:50 +02:00
// Load text lines
static char * * LoadTextLines ( const char * text , int * count )
2025-08-01 19:40:44 +02:00
{
2025-08-03 01:17:50 +02:00
# define MAX_TEXT_LINES 512
# define MAX_TEXT_LINE_LEN 256
2025-08-01 19:40:44 +02:00
2025-08-03 01:17:50 +02:00
char * * lines = ( char * * ) RL_CALLOC ( MAX_TEXT_LINES , sizeof ( char * ) ) ;
for ( int i = 0 ; i < MAX_TEXT_LINES ; i + + ) lines [ i ] = ( char * ) RL_CALLOC ( MAX_TEXT_LINE_LEN , 1 ) ;
2025-08-01 19:40:44 +02:00
int textSize = ( int ) strlen ( text ) ;
2025-08-03 01:17:50 +02:00
int k = 0 ;
2025-08-01 19:40:44 +02:00
2025-08-03 01:17:50 +02:00
for ( int i = 0 , len = 0 ; ( i < textSize ) & & ( k < MAX_TEXT_LINES ) ; i + + )
2025-08-01 19:40:44 +02:00
{
if ( text [ i ] = = ' \n ' )
{
2025-08-03 01:17:50 +02:00
strncpy ( lines [ k ] , & text [ i - len ] , len ) ;
2025-08-01 19:40:44 +02:00
len = 0 ;
2025-08-03 01:17:50 +02:00
k + + ;
2025-08-01 19:40:44 +02:00
}
else len + + ;
}
2025-08-03 01:17:50 +02:00
* count + = k ;
return lines ;
}
// Unload text lines
static void UnloadTextLines ( char * * lines )
{
for ( int i = 0 ; i < MAX_TEXT_LINES ; i + + ) RL_FREE ( lines [ i ] ) ;
RL_FREE ( lines ) ;
2025-08-01 19:40:44 +02:00
}
2025-08-14 20:38:27 +02:00
// Get example info from example file header
// NOTE: Expecting the example to follow raylib_example_template.c
2025-08-06 14:04:30 +02:00
rlExampleInfo * LoadExampleInfo ( const char * exFileName )
2025-08-06 00:56:29 +02:00
{
rlExampleInfo * exInfo = ( rlExampleInfo * ) RL_CALLOC ( 1 , sizeof ( rlExampleInfo ) ) ;
2025-08-14 20:38:27 +02:00
if ( FileExists ( exFileName ) & & IsFileExtension ( exFileName , " .c " ) )
2025-08-06 00:56:29 +02:00
{
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: ⭐️
2025-08-06 14:04:30 +02:00
// WARNING: The stars unicode in examples is not the same than in collection list!!!
2025-08-06 00:56:29 +02:00
int starsIndex = TextFindIndex ( exText , " ★ " ) ;
if ( starsIndex > 0 )
{
const char * starPtr = exText + starsIndex ;
while ( * starPtr )
{
if ( ( ( unsigned char ) starPtr [ 0 ] = = 0xe2 ) & &
2025-08-06 14:04:30 +02:00
( ( unsigned char ) starPtr [ 1 ] = = 0x98 ) & &
( ( unsigned char ) starPtr [ 2 ] = = 0x85 ) )
2025-08-06 00:56:29 +02:00
{
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
2025-08-06 14:04:30 +02:00
// NOTE: Using copyright line instead of "Example contributed by " because
// most examples do not contain that line --> TODO: Review examples header formating?
// Expected format: Copyright (c) <year_created>-<year_updated> <user_name> (@<user_github>)
// Alternatives: Copyright (c) <year_created> <author_name> (@<user_github>) and <contrib_name> (@<contrib_user>)
int copyrightIndex = TextFindIndex ( exText , " Copyright (c) " ) ;
int yearStartIndex = copyrightIndex + 14 ;
int yearEndIndex = TextFindIndex ( exText + yearStartIndex , " " ) ;
int authorStartIndex = yearStartIndex + yearEndIndex + 1 ;
int authorEndIndex = TextFindIndex ( exText + authorStartIndex , " (@ " ) ;
if ( authorEndIndex ! = - 1 ) // Github user also available
2025-08-06 00:56:29 +02:00
{
2025-08-06 14:04:30 +02:00
authorEndIndex + = authorStartIndex ;
strncpy ( exInfo - > author , exText + authorStartIndex , authorEndIndex - authorStartIndex ) ;
// Get GitHub user
int userStartIndex = authorEndIndex + 3 ;
int userEndIndex = TextFindIndex ( exText + userStartIndex , " ) " ) ;
userEndIndex + = userStartIndex ;
strncpy ( exInfo - > authorGitHub , exText + userStartIndex , userEndIndex - userStartIndex ) ;
2025-08-06 00:56:29 +02:00
}
2025-08-06 14:04:30 +02:00
else // GitHub user not found to set end, using '\n'
2025-08-06 00:56:29 +02:00
{
2025-08-06 14:04:30 +02:00
authorEndIndex = TextFindIndex ( exText + authorStartIndex , " \n " ) ;
authorEndIndex + = authorStartIndex ;
strncpy ( exInfo - > author , exText + authorStartIndex , authorEndIndex - authorStartIndex ) ;
2025-08-06 00:56:29 +02:00
}
UnloadFileText ( exText ) ;
2025-08-19 10:38:07 +02:00
exInfo - > resPaths = ScanExampleResources ( exFileName , & exInfo - > resCount ) ;
2025-08-06 00:56:29 +02:00
}
return exInfo ;
}
2025-08-06 14:04:30 +02:00
// Unload example information
static void UnloadExampleInfo ( rlExampleInfo * exInfo )
{
2025-08-19 10:38:07 +02:00
ClearExampleResources ( exInfo - > resPaths ) ;
2025-08-06 14:04:30 +02:00
RL_FREE ( exInfo ) ;
}
2025-08-01 19:40:44 +02:00
// raylib example line info parser
2025-08-02 18:00:41 +02:00
// Parses following line format: core;core_basic_window;⭐️☆☆☆;1.0;1.0;"Ray";@raysan5
2025-08-01 19:40:44 +02:00
static int ParseExampleInfoLine ( const char * line , rlExampleInfo * entry )
{
# define MAX_EXAMPLE_INFO_LINE_LEN 512
char temp [ MAX_EXAMPLE_INFO_LINE_LEN ] = { 0 } ;
2025-08-03 01:17:50 +02:00
strncpy ( temp , line , MAX_EXAMPLE_INFO_LINE_LEN ) ;
2025-08-01 19:40:44 +02:00
temp [ MAX_EXAMPLE_INFO_LINE_LEN - 1 ] = ' \0 ' ; // Ensure null termination
int tokenCount = 0 ;
char * * tokens = TextSplit ( line , ' ; ' , & tokenCount ) ;
// Get category and name
2025-08-02 18:00:41 +02:00
strcpy ( entry - > category , tokens [ 0 ] ) ;
strcpy ( entry - > name , tokens [ 1 ] ) ;
2025-08-01 19:40:44 +02:00
// Parsing stars
// NOTE: Counting the unicode char occurrences: ⭐️
2025-08-06 00:56:29 +02:00
const char * starPtr = tokens [ 2 ] ;
while ( * starPtr )
2025-08-01 19:40:44 +02:00
{
2025-08-06 00:56:29 +02:00
if ( ( ( unsigned char ) starPtr [ 0 ] = = 0xe2 ) & &
( ( unsigned char ) starPtr [ 1 ] = = 0xad ) & &
( ( unsigned char ) starPtr [ 2 ] = = 0x90 ) )
2025-08-01 19:40:44 +02:00
{
entry - > stars + + ;
2025-08-06 00:56:29 +02:00
starPtr + = 3 ; // Advance past multibyte character
2025-08-01 19:40:44 +02:00
}
2025-08-06 00:56:29 +02:00
else starPtr + + ;
2025-08-01 19:40:44 +02:00
}
// Get raylib creation/update versions
entry - > verCreated = strtof ( tokens [ 3 ] , NULL ) ;
entry - > verUpdated = strtof ( tokens [ 4 ] , NULL ) ;
2025-08-03 01:17:50 +02:00
// Get author and github
if ( tokens [ 5 ] [ 0 ] = = ' " ' ) tokens [ 5 ] + = 1 ;
if ( tokens [ 5 ] [ strlen ( tokens [ 5 ] ) - 1 ] = = ' " ' ) tokens [ 5 ] [ strlen ( tokens [ 5 ] ) - 1 ] = ' \0 ' ;
strcpy ( entry - > author , tokens [ 5 ] ) ;
2025-08-06 14:04:30 +02:00
strcpy ( entry - > authorGitHub , tokens [ 6 ] + 1 ) ; // Skip '@'
2025-08-01 19:40:44 +02:00
return 1 ;
}
// Text compare, required for qsort() function
2025-08-02 18:00:41 +02:00
static int rlExampleInfoCompare ( const void * a , const void * b )
2025-08-01 19:40:44 +02:00
{
2025-08-02 18:00:41 +02:00
const rlExampleInfo * ex1 = ( const rlExampleInfo * ) a ;
const rlExampleInfo * ex2 = ( const rlExampleInfo * ) b ;
2025-08-01 19:40:44 +02:00
2025-08-02 18:00:41 +02:00
return strcmp ( ex1 - > name , ex2 - > name ) ;
2025-08-01 19:40:44 +02:00
}
// Sort array of strings by name
// WARNING: items[] pointers are reorganized
2025-08-02 18:00:41 +02:00
static void SortExampleByName ( rlExampleInfo * items , int count )
2025-08-01 19:40:44 +02:00
{
2025-08-02 18:00:41 +02:00
qsort ( items , count , sizeof ( rlExampleInfo ) , rlExampleInfoCompare ) ;
2025-08-01 19:40:44 +02:00
}
2025-08-04 17:51:48 +02:00
// Scan resource paths in example file
2025-08-04 19:27:48 +02:00
// WARNING: Supported resource file extensions is hardcoded by used file types
// but new examples could require other file extensions to be added,
// maybe it should look for '.xxx")' patterns instead
2025-08-04 17:51:48 +02:00
static char * * ScanExampleResources ( const char * filePath , int * resPathCount )
{
2025-08-04 19:27:48 +02:00
# define REXM_MAX_RESOURCE_PATH_LEN 256
2025-08-04 17:51:48 +02:00
char * * paths = ( char * * ) RL_CALLOC ( REXM_MAX_RESOURCE_PATHS , sizeof ( char * * ) ) ;
2025-08-04 19:27:48 +02:00
for ( int i = 0 ; i < REXM_MAX_RESOURCE_PATHS ; i + + ) paths [ i ] = ( char * ) RL_CALLOC ( REXM_MAX_RESOURCE_PATH_LEN , sizeof ( char ) ) ;
2025-08-04 17:51:48 +02:00
int resCounter = 0 ;
char * code = LoadFileText ( filePath ) ;
if ( code ! = NULL )
{
// Resources extensions to check
2025-08-04 19:27:48 +02:00
const char * exts [ ] = { " .png " , " .bmp " , " .jpg " , " .qoi " , " .gif " , " .raw " , " .hdr " , " .ttf " , " .fnt " , " .wav " , " .ogg " , " .mp3 " , " .flac " , " .mod " , " .qoa " , " .qoa " , " .obj " , " .iqm " , " .glb " , " .m3d " , " .vox " , " .vs " , " .fs " , " .txt " } ;
const int extCount = sizeof ( exts ) / sizeof ( char * ) ;
2025-08-04 17:51:48 +02:00
char * ptr = code ;
while ( ( ptr = strchr ( ptr , ' " ' ) ) ! = NULL )
{
char * start = ptr + 1 ;
char * end = strchr ( start , ' " ' ) ;
if ( ! end ) break ;
2025-08-19 10:38:07 +02:00
// WARNING: Some paths could be for saving files, not loading, those "resource" files must be omitted
2025-08-17 21:31:05 +02:00
// HACK: Just check previous position from pointer for function name including the string...
2025-08-19 10:38:07 +02:00
// This is a dirty solution, the good one would be getting the data loading function names...
2025-08-17 21:31:05 +02:00
if ( TextFindIndex ( ptr - 40 , " ExportImage " ) = = - 1 )
2025-08-04 17:51:48 +02:00
{
2025-08-17 21:31:05 +02:00
int len = ( int ) ( end - start ) ;
if ( ( len > 0 ) & & ( len < REXM_MAX_RESOURCE_PATH_LEN ) )
{
char buffer [ REXM_MAX_RESOURCE_PATH_LEN ] = { 0 } ;
strncpy ( buffer , start , len ) ;
buffer [ len ] = ' \0 ' ;
2025-08-04 17:51:48 +02:00
2025-08-17 21:31:05 +02:00
// TODO: Make sure buffer is a path (and not a Tracelog() text)
2025-08-14 20:38:27 +02:00
2025-08-17 21:31:05 +02:00
// Check for known extensions
for ( int i = 0 ; i < extCount ; i + + )
2025-08-04 17:51:48 +02:00
{
2025-08-17 21:31:05 +02:00
// TODO: WARNING: IsFileExtension() expects a NULL terminated fileName,
// but in this case buffer can contain any kind of string,
// including not paths strings, for example TraceLog() string
if ( IsFileExtension ( buffer , exts [ i ] ) )
2025-08-04 17:51:48 +02:00
{
2025-08-17 21:31:05 +02:00
// Avoid duplicates
bool found = false ;
for ( int j = 0 ; j < resCounter ; j + + )
{
if ( strcmp ( paths [ j ] , buffer ) = = 0 ) { found = true ; break ; }
}
2025-08-04 17:51:48 +02:00
2025-08-17 21:31:05 +02:00
if ( ! found & & ( resCounter < REXM_MAX_RESOURCE_PATHS ) )
{
strcpy ( paths [ resCounter ] , buffer ) ;
resCounter + + ;
}
2025-08-04 17:51:48 +02:00
2025-08-17 21:31:05 +02:00
break ;
}
2025-08-04 17:51:48 +02:00
}
}
}
ptr = end + 1 ;
}
UnloadFileText ( code ) ;
}
* resPathCount = resCounter ;
return paths ;
}
// Clear resource paths scanned
static void ClearExampleResources ( char * * resPaths )
{
for ( int i = 0 ; i < REXM_MAX_RESOURCE_PATHS ; i + + ) RL_FREE ( resPaths [ i ] ) ;
RL_FREE ( resPaths ) ;
}
2025-08-07 17:03:54 +02:00
2025-08-19 10:38:07 +02:00
// Add VS project (.vcxproj) to existing VS solution (.sln)
2025-08-10 09:54:45 +02:00
// WARNING: Adding a .vcxproj to .sln can not be automated with:
// - "dotnet" tool (C# projects only)
// - "devenv" tool (no adding support, only building)
// It must be done manually editing the .sln file
2025-08-19 10:38:07 +02:00
static int AddVSProjectToSolution ( const char * projFile , const char * slnFile , const char * category )
2025-08-07 17:03:54 +02:00
{
int result = 0 ;
2025-08-13 13:25:27 +02:00
// WARNING: Function uses extensively TextFormat(),
// *projFile ptr will be overwriten after a while
2025-08-13 12:33:12 +02:00
// Generate unique UUID
2025-08-19 10:38:07 +02:00
const char * uuid = GenerateUUIDv4 ( ) ;
2025-08-13 12:33:12 +02:00
// Replace default UUID (core_basic_window) on project file by new one
FileTextReplace ( projFile , " 0981CA98-E4A5-4DF1-987F-A41D09131EFC " , uuid ) ;
2025-08-07 17:03:54 +02:00
2025-08-13 12:33:12 +02:00
char * slnText = LoadFileText ( slnFile ) ;
char * slnTextUpdated = ( char * ) RL_CALLOC ( REXM_MAX_BUFFER_SIZE , 1 ) ;
// Add project to solution
//----------------------------------------------------------------------------------------
2025-08-07 17:03:54 +02:00
// Format: Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "<project_name>", "examples\<project_name>.vcxproj", "{<project_uuid>}"
2025-08-13 12:33:12 +02:00
// NOTE: Find a position to insert new project: At the end of the projects list, same strategy as VS2022 "Add Project"
int prjStartIndex = TextFindIndex ( slnText , " Global " ) ;
// Add new project info
// WARNING: UUID can actually be duplicated and it still works...
strncpy ( slnTextUpdated , slnText , prjStartIndex ) ;
int offsetIndex = sprintf ( slnTextUpdated + prjStartIndex ,
TextFormat ( " Project( \" {8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942} \" ) = \" %s \" , \" examples \\ %s \" , \" {%s} \" \n " ,
GetFileNameWithoutExt ( projFile ) , GetFileName ( projFile ) , uuid ) ) ;
offsetIndex + = prjStartIndex ;
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , " EndProject \n " ) ;
//----------------------------------------------------------------------------------------
// Update project config
//----------------------------------------------------------------------------------------
// Find position to add project config: At the end of global section, same strategy as VS2022 "Add Project"
int projConfStartIndex = TextFindIndex ( slnText , " GlobalSection(ProjectConfigurationPlatforms) = postSolution " ) ;
strncpy ( slnTextUpdated + offsetIndex , slnText + prjStartIndex , projConfStartIndex - prjStartIndex ) ;
offsetIndex + = ( projConfStartIndex - prjStartIndex ) ;
int projConfEndIndex = TextFindIndex ( slnText + projConfStartIndex , " EndGlobalSection " ) ;
projConfEndIndex + = projConfStartIndex ;
strncpy ( slnTextUpdated + offsetIndex , slnText + projConfStartIndex , projConfEndIndex - projConfStartIndex ) ;
offsetIndex + = ( projConfEndIndex - projConfStartIndex ) ;
// Add project config lines
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , TextFormat ( " \t {%s}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 \n " , uuid ) ) ;
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , TextFormat ( " \t \t {%s}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 \n " , uuid ) ) ;
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , TextFormat ( " \t \t {%s}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 \n " , uuid ) ) ;
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , TextFormat ( " \t \t {%s}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 \n " , uuid ) ) ;
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , TextFormat ( " \t \t {%s}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 \n " , uuid ) ) ;
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , TextFormat ( " \t \t {%s}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 \n " , uuid ) ) ;
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , TextFormat ( " \t \t {%s}.Debug|ARM64.ActiveCfg = Debug|ARM64 \n " , uuid ) ) ;
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , TextFormat ( " \t \t {%s}.Debug|ARM64.Build.0 = Debug|ARM64 \n " , uuid ) ) ;
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , TextFormat ( " \t \t {%s}.Debug|x64.ActiveCfg = Debug|x64 \n " , uuid ) ) ;
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , TextFormat ( " \t \t {%s}.Debug|x64.Build.0 = Debug|x64 \n " , uuid ) ) ;
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , TextFormat ( " \t \t {%s}.Debug|x86.ActiveCfg = Debug|Win32 \n " , uuid ) ) ;
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , TextFormat ( " \t \t {%s}.Debug|x86.Build.0 = Debug|Win32 \n " , uuid ) ) ;
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , TextFormat ( " \t \t {%s}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 \n " , uuid ) ) ;
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , TextFormat ( " \t \t {%s}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 \n " , uuid ) ) ;
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , TextFormat ( " \t \t {%s}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 \n " , uuid ) ) ;
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , TextFormat ( " \t \t {%s}.Release.DLL|x64.Build.0 = Release.DLL|x64 \n " , uuid ) ) ;
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , TextFormat ( " \t \t {%s}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 \n " , uuid ) ) ;
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , TextFormat ( " \t \t {%s}.Release.DLL|x86.Build.0 = Release.DLL|Win32 \n " , uuid ) ) ;
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , TextFormat ( " \t \t {%s}.Release|ARM64.ActiveCfg = Release|ARM64 \n " , uuid ) ) ;
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , TextFormat ( " \t \t {%s}.Release|ARM64.Build.0 = Release|ARM64 \n " , uuid ) ) ;
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , TextFormat ( " \t \t {%s}.Release|x64.ActiveCfg = Release|x64 \n " , uuid ) ) ;
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , TextFormat ( " \t \t {%s}.Release|x64.Build.0 = Release|x64 \n " , uuid ) ) ;
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , TextFormat ( " \t \t {%s}.Release|x86.ActiveCfg = Release|Win32 \n " , uuid ) ) ;
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , TextFormat ( " \t \t {%s}.Release|x86.Build.0 = Release|Win32 \n " , uuid ) ) ;
// Write next section directly to avoid copy logic
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , " \t EndGlobalSection \n " ) ;
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , " \t GlobalSection(SolutionProperties) = preSolution \n " ) ;
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , " \t \t HideSolutionNode = FALSE \n " ) ;
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , " \t EndGlobalSection \n \t " ) ;
//----------------------------------------------------------------------------------------
// Place project to explorer folder
//----------------------------------------------------------------------------------------
// Find position to add project folder: At the end of global section, same strategy as VS2022 "Add Project"
int projFolderStartIndex = TextFindIndex ( slnText , " GlobalSection(NestedProjects) = preSolution " ) ;
int projFolderEndIndex = TextFindIndex ( slnText + projFolderStartIndex , " \t EndGlobalSection " ) ;
projFolderEndIndex + = projFolderStartIndex ;
strncpy ( slnTextUpdated + offsetIndex , slnText + projFolderStartIndex , projFolderEndIndex - projFolderStartIndex ) ;
offsetIndex + = ( projFolderEndIndex - projFolderStartIndex ) ;
// Add project folder line
// NOTE: Folder uuid depends on category
if ( strcmp ( category , " core " ) = = 0 ) offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , TextFormat ( " \t \t {%s} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} \n " , uuid ) ) ;
else if ( strcmp ( category , " shapes " ) = = 0 ) offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , TextFormat ( " \t \t { % s } = { 278 D8859 - 20 B1 - 428F - 8448 - 064F 46E1 F021 } \ n " , uuid));
else if ( strcmp ( category , " textures " ) = = 0 ) offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , TextFormat ( " \t \t { % s } = { DA049009 - 21FF - 4 AC0 - 84E4 - 830 DD1BCD0CE } \ n " , uuid));
else if ( strcmp ( category , " text " ) = = 0 ) offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , TextFormat ( " \t \t { % s } = { 8 D3C83B7 - F1E0 - 4 C2E - 9E34 - EE5F6AB2502A } \ n " , uuid));
else if ( strcmp ( category , " models " ) = = 0 ) offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , TextFormat ( " \t \t { % s } = { AF5BEC5C - 1F 2 B - 4 DA8 - B12D - D09FE569237C } \ n " , uuid));
else if ( strcmp ( category , " shaders " ) = = 0 ) offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , TextFormat ( " \t \t { % s } = { 5317807F - 61 D4 - 4E0 F - B6DC - 2 D9F12621ED9 } \ n " , uuid));
else if ( strcmp ( category , " audio " ) = = 0 ) offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , TextFormat ( " \t \t { % s } = { CC132A4D - D081 - 4 C26 - BFB9 - AB11984054F8 } \ n " , uuid));
else if ( strcmp ( category , " other " ) = = 0 ) offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , TextFormat ( " \t \t { % s } = { E9D708A5 - 9 C1F - 4 B84 - A795 - C5F191801762 } \ n " , uuid));
else LOG ( " WARNING: Provided category is not valid: %s \n " , category ) ;
//----------------------------------------------------------------------------------------
// Write end of file, no need to copy from original file
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , " \t EndGlobalSection \n " ) ;
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , " \t GlobalSection(ExtensibilityGlobals) = postSolution \n " ) ;
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , " \t \t SolutionGuid = {E926C768-6307-4423-A1EC-57E95B1FAB29} \n " ) ;
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , " \t EndGlobalSection \n " ) ;
offsetIndex + = sprintf ( slnTextUpdated + offsetIndex , " EndGlobal \n " ) ;
SaveFileText ( slnFile , slnTextUpdated ) ;
UnloadFileText ( slnText ) ;
RL_FREE ( slnTextUpdated ) ;
2025-08-07 17:03:54 +02:00
return result ;
}
2025-08-13 13:25:27 +02:00
// Generate unique UUID v4 string
// Output format: {9A2F48CC-0DA8-47C0-884E-02E37F9BE6C1}
const char * GenerateUUIDv4 ( void )
{
static char uuid [ 38 ] = { 0 } ;
memset ( uuid , 0 , 38 ) ;
unsigned char bytes [ 16 ] = { 0 } ;
for ( int i = 0 ; i < 16 ; i + + ) bytes [ i ] = ( unsigned char ) GetRandomValue ( 0 , 255 ) ;
// Set version (4) and variant (RFC 4122)
bytes [ 6 ] = ( bytes [ 6 ] & 0x0F ) | 0x40 ; // Version
bytes [ 8 ] = ( bytes [ 8 ] & 0x3F ) | 0x80 ; // Variant
snprintf ( uuid , 38 ,
" %02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X " ,
bytes [ 0 ] , bytes [ 1 ] , bytes [ 2 ] , bytes [ 3 ] ,
bytes [ 4 ] , bytes [ 5 ] ,
bytes [ 6 ] , bytes [ 7 ] ,
bytes [ 8 ] , bytes [ 9 ] ,
bytes [ 10 ] , bytes [ 11 ] , bytes [ 12 ] , bytes [ 13 ] , bytes [ 14 ] , bytes [ 15 ] ) ;
return uuid ;
}