rexm.c 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729
  1. /*******************************************************************************************
  2. *
  3. * rexm [raylib examples manager] - A simple command-line tool to manage raylib examples
  4. *
  5. * Supported processes:
  6. * - create <new_example_name>
  7. * - add <example_name>
  8. * - rename <old_examples_name> <new_example_name>
  9. * - remove <example_name>
  10. * - validate
  11. *
  12. * Files involved in the processes:
  13. * - raylib/examples/<category>/<category>_example_name.c
  14. * - raylib/examples/<category>/<category>_example_name.png
  15. * - raylib/examples/<category>/resources/..
  16. * - raylib/examples/Makefile
  17. * - raylib/examples/Makefile.Web
  18. * - raylib/examples/README.md
  19. * - raylib/projects/VS2022/examples/<category>_example_name.vcxproj
  20. * - raylib/projects/VS2022/raylib.sln
  21. * - raylib.com/common/examples.js
  22. * - raylib.com/examples/<category>/<category>_example_name.html
  23. * - raylib.com/examples/<category>/<category>_example_name.data
  24. * - raylib.com/examples/<category>/<category>_example_name.wasm
  25. * - raylib.com/examples/<category>/<category>_example_name.js
  26. *
  27. * LICENSE: zlib/libpng
  28. *
  29. * Copyright (c) 2025 Ramon Santamaria (@raysan5)
  30. *
  31. * This software is provided "as-is", without any express or implied warranty. In no event
  32. * will the authors be held liable for any damages arising from the use of this software.
  33. *
  34. * Permission is granted to anyone to use this software for any purpose, including commercial
  35. * applications, and to alter it and redistribute it freely, subject to the following restrictions:
  36. *
  37. * 1. The origin of this software must not be misrepresented; you must not claim that you
  38. * wrote the original software. If you use this software in a product, an acknowledgment
  39. * in the product documentation would be appreciated but is not required.
  40. *
  41. * 2. Altered source versions must be plainly marked as such, and must not be misrepresented
  42. * as being the original software.
  43. *
  44. * 3. This notice may not be removed or altered from any source distribution.
  45. *
  46. **********************************************************************************************/
  47. #include "raylib.h"
  48. #include <stdlib.h>
  49. #include <stdio.h> // Required for: rename(), remove()
  50. #include <string.h> // Required for: strcmp(), strcpy()
  51. #define SUPPORT_LOG_INFO
  52. #if defined(SUPPORT_LOG_INFO) && defined(_DEBUG)
  53. #define LOG(...) printf(__VA_ARGS__)
  54. #else
  55. #define LOG(...)
  56. #endif
  57. //----------------------------------------------------------------------------------
  58. // Types and Structures Definition
  59. //----------------------------------------------------------------------------------
  60. // raylib example info struct
  61. typedef struct {
  62. char category[16];
  63. char name[128];
  64. char stars;
  65. float verCreated;
  66. float verUpdated;
  67. char author[64];
  68. char authorGitHub[64];
  69. } rlExampleInfo;
  70. // Example management operations
  71. typedef enum {
  72. OP_NONE = 0, // No process to do
  73. OP_CREATE = 1, // Create new example, using default template
  74. OP_ADD = 2, // Add existing examples (hopefully following template)
  75. OP_RENAME = 3, // Rename existing example
  76. OP_REMOVE = 4, // Remove existing example
  77. OP_VALIDATE = 5, // Validate examples, using [examples_list.txt] as main source by default
  78. } rlExampleOperation;
  79. #define MAX_EXAMPLE_CATEGORIES 8
  80. static const char *exCategories[MAX_EXAMPLE_CATEGORIES] = { "core", "shapes", "textures", "text", "models", "shaders", "audio", "others" };
  81. //----------------------------------------------------------------------------------
  82. // Module specific functions declaration
  83. //----------------------------------------------------------------------------------
  84. static int FileTextReplace(const char *fileName, const char *textLookUp, const char *textReplace);
  85. static int FileCopy(const char *srcPath, const char *dstPath);
  86. static int FileRename(const char *fileName, const char *fileRename);
  87. static int FileRemove(const char *fileName);
  88. // Load examples collection information
  89. // NOTE 1: Load by category: "ALL", "core", "shapes", "textures", "text", "models", "shaders", others"
  90. // NOTE 2: Sort examples list on request flag
  91. static rlExampleInfo *LoadExamplesData(const char *fileName, const char *category, bool sort, int *exCount);
  92. static void UnloadExamplesData(rlExampleInfo *exInfo);
  93. // Get text lines (by line-breaks '\n')
  94. // WARNING: It does not copy text data, just returns line pointers
  95. static const char **GetTextLines(const char *text, int *count);
  96. // raylib example line info parser
  97. // Parses following line format: core/core_basic_window;⭐️☆☆☆;1.0;1.0;"Ray"/@raysan5
  98. static int ParseExampleInfoLine(const char *line, rlExampleInfo *entry);
  99. // Sort array of strings by name
  100. // WARNING: items[] pointers are reorganized
  101. static void SortExampleByName(rlExampleInfo *items, int count);
  102. //------------------------------------------------------------------------------------
  103. // Program main entry point
  104. //------------------------------------------------------------------------------------
  105. int main(int argc, char *argv[])
  106. {
  107. // Paths required for examples management
  108. // TODO: Avoid hardcoding path values...
  109. char *exBasePath = "C:/GitHub/raylib/examples";
  110. char *exWebPath = "C:/GitHub/raylib.com/examples";
  111. char *exTemplateFilePath = "C:/GitHub/raylib/examples/examples_template.c";
  112. char *exTemplateScreenshot = "C:/GitHub/raylib/examples/examples_template.png";
  113. char *exCollectionListPath = "C:/GitHub/raylib/examples/examples_list.txt";
  114. char inFileName[1024] = { 0 }; // Example input filename (to be added)
  115. char exName[64] = { 0 }; // Example name, without extension: core_basic_window
  116. char exCategory[32] = { 0 }; // Example category: core
  117. char exRename[64] = { 0 }; // Example re-name, without extension
  118. int opCode = OP_NONE; // Operation code: 0-None(Help), 1-Create, 2-Add, 3-Rename, 4-Remove
  119. // Command-line usage mode
  120. //--------------------------------------------------------------------------------------
  121. if (argc > 1)
  122. {
  123. // Supported commands:
  124. // help : Provides command-line usage information (default)
  125. // create <new_example_name> : Creates an empty example, from internal template
  126. // add <example_name> : Add existing example, category extracted from name
  127. // rename <old_examples_name> <new_example_name> : Rename an existing example
  128. // remove <example_name> : Remove an existing example
  129. // validate : Validate examples collection
  130. if (strcmp(argv[1], "create") == 0)
  131. {
  132. // Check for valid upcoming argument
  133. if (argc == 2) LOG("WARNING: No filename provided to create\n");
  134. else if (argc > 3) LOG("WARNING: Too many arguments provided\n");
  135. else
  136. {
  137. // TODO: Additional security checks for file name?
  138. strcpy(exName, argv[2]); // Register filename for new example creation
  139. strncpy(exCategory, exName, TextFindIndex(exName, "_"));
  140. opCode = 1;
  141. }
  142. }
  143. else if (strcmp(argv[1], "add") == 0)
  144. {
  145. // Check for valid upcoming argument
  146. if (argc == 2) LOG("WARNING: No filename provided to create\n");
  147. else if (argc > 3) LOG("WARNING: Too many arguments provided\n");
  148. else
  149. {
  150. if (IsFileExtension(argv[2], ".c")) // Check for valid file extension: input
  151. {
  152. if (FileExists(inFileName))
  153. {
  154. strcpy(inFileName, argv[2]); // Register filename for addition
  155. strcpy(exName, GetFileNameWithoutExt(argv[2])); // Register example name
  156. strncpy(exCategory, exName, TextFindIndex(exName, "_"));
  157. opCode = 2;
  158. }
  159. else LOG("WARNING: Input file not found, include path\n");
  160. }
  161. else LOG("WARNING: Input file extension not recognized (.c)\n");
  162. }
  163. }
  164. else if (strcmp(argv[1], "rename") == 0)
  165. {
  166. if (argc == 2) LOG("WARNING: No filename provided to be renamed\n");
  167. else if (argc > 4) LOG("WARNING: Too many arguments provided\n");
  168. else
  169. {
  170. strcpy(exName, argv[2]); // Register example name
  171. strncpy(exCategory, exName, TextFindIndex(exName, "_"));
  172. strcpy(exRename, argv[3]);
  173. // TODO: Consider rename with change of category
  174. opCode = 3;
  175. }
  176. }
  177. else if (strcmp(argv[1], "remove") == 0)
  178. {
  179. // Check for valid upcoming argument
  180. if (argc == 2) LOG("WARNING: No filename provided to create\n");
  181. else if (argc > 3) LOG("WARNING: Too many arguments provided\n");
  182. else
  183. {
  184. strcpy(exName, argv[2]); // Register filename for removal
  185. opCode = 4;
  186. }
  187. }
  188. else if (strcmp(argv[1], "validate") == 0)
  189. {
  190. opCode = 5;
  191. }
  192. }
  193. // Load examples collection information
  194. //exInfo = LoadExamplesData(exCollectionListPath, "core", true, &exInfoCount);
  195. //for (int i = 0; i < exInfoCount; i++) printf("%i - %s [%i]\n", i + 1, exInfo[i].name, exInfo[i].stars);
  196. switch (opCode)
  197. {
  198. case 1: // Create: New example from template
  199. {
  200. // Create: raylib/examples/<category>/<category>_example_name.c
  201. char *exText = LoadFileText(exTemplateFilePath);
  202. char *exTextUpdated[6] = { 0 };
  203. int exIndex = TextFindIndex(exText, "/****************");
  204. exTextUpdated[0] = TextReplace(exText + exIndex, "<module>", exCategory);
  205. exTextUpdated[1] = TextReplace(exTextUpdated[0], "<name>", exName + strlen(exCategory) + 1);
  206. //TextReplace(newExample, "<user_name>", "Ray");
  207. //TextReplace(newExample, "@<user_github>", "@raysan5");
  208. //TextReplace(newExample, "<year_created>", 2025);
  209. //TextReplace(newExample, "<year_updated>", 2025);
  210. SaveFileText(TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName), exTextUpdated[1]);
  211. for (int i = 0; i < 6; i++) { MemFree(exTextUpdated[i]); exTextUpdated[i] = NULL; }
  212. UnloadFileText(exText);
  213. }
  214. case 2: // Add: Example from command-line input filename
  215. {
  216. // Create: raylib/examples/<category>/<category>_example_name.c
  217. if (opCode != 1) FileCopy(inFileName, TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName));
  218. // Create: raylib/examples/<category>/<category>_example_name.png
  219. FileCopy(exTemplateScreenshot, TextFormat("%s/%s/%s.png", exBasePath, exCategory, exName)); // WARNING: To be updated manually!
  220. // Copy: raylib/examples/<category>/resources/... // WARNING: To be updated manually!
  221. // Add example to the main collection list, if not already there
  222. // NOTE: Required format: shapes;shapes_basic_shapes;⭐️☆☆☆;1.0;4.2;"Ray";@raysan5
  223. //------------------------------------------------------------------------------------------------
  224. char *exColInfo = LoadFileText(exCollectionListPath);
  225. if (TextFindIndex(exColInfo, exName) == -1) // Example not found
  226. {
  227. char *exColInfoUpdated = (char *)RL_CALLOC(2*1024*1024, 1); // Updated list copy, 2MB
  228. // Add example to the main list, by category
  229. // by default add it last in the category list
  230. // NOTE: When populating to other files, lists are sorted by name
  231. int nextCatIndex = 0;
  232. if (strcmp(exCategory, "core") == 0) nextCatIndex = 1;
  233. else if (strcmp(exCategory, "shapes") == 0) nextCatIndex = 2;
  234. else if (strcmp(exCategory, "textures") == 0) nextCatIndex = 3;
  235. else if (strcmp(exCategory, "text") == 0) nextCatIndex = 4;
  236. else if (strcmp(exCategory, "models") == 0) nextCatIndex = 5;
  237. else if (strcmp(exCategory, "shaders") == 0) nextCatIndex = 6;
  238. else if (strcmp(exCategory, "audio") == 0) nextCatIndex = 7;
  239. else if (strcmp(exCategory, "others") == 0) nextCatIndex = -1; // Add to EOF
  240. if (nextCatIndex == -1)
  241. {
  242. // Add example to the end of the list
  243. int endIndex = strlen(exColInfo);
  244. memcpy(exColInfoUpdated, exColInfo, endIndex);
  245. sprintf(exColInfoUpdated + endIndex, TextFormat("\n%s/%s\n", exCategory, exName));
  246. }
  247. else
  248. {
  249. // Add example to the end of the category list
  250. // TODO: Get required example info from example file header (if provided)
  251. // NOTE: If no example info is provided (other than category/name), just using some default values
  252. int catIndex = TextFindIndex(exColInfo, exCategories[nextCatIndex]);
  253. memcpy(exColInfoUpdated, exColInfo, catIndex);
  254. int textWritenSize = sprintf(exColInfoUpdated + catIndex, TextFormat("%s;%s;⭐️☆☆☆;6.0;6.0;\"Ray\";@raysan5\n", exCategory, exName));
  255. memcpy(exColInfoUpdated + catIndex + textWritenSize, exColInfo + catIndex, strlen(exColInfo) - catIndex);
  256. }
  257. SaveFileText(exCollectionListPath, exColInfoUpdated);
  258. RL_FREE(exColInfoUpdated);
  259. }
  260. UnloadFileText(exColInfo);
  261. //------------------------------------------------------------------------------------------------
  262. // Edit: raylib/examples/Makefile --> Add new example
  263. //------------------------------------------------------------------------------------------------
  264. char *mkText = LoadFileText(TextFormat("%s/Makefile", exBasePath));
  265. char *mkTextUpdated = (char *)RL_CALLOC(2*1024*1024, 1); // Updated Makefile copy, 2MB
  266. int exListStartIndex = TextFindIndex(mkText, "#EXAMPLES_LIST_START");
  267. int exListEndIndex = TextFindIndex(mkText, "#EXAMPLES_LIST_END");
  268. int mkIndex = exListStartIndex;
  269. memcpy(mkTextUpdated, mkText, exListStartIndex);
  270. TextAppend(mkTextUpdated + mkIndex, "#EXAMPLES_LIST_START\n", &mkIndex);
  271. for (int i = 0, exCount = 0; i < MAX_EXAMPLE_CATEGORIES; i++)
  272. {
  273. TextAppend(mkTextUpdated + mkIndex, TextFormat("%s = \\\n", TextToUpper(exCategories[i])), &mkIndex); // Category Makefile object ("CORE = \")
  274. rlExampleInfo *exCatList = LoadExamplesData(exCollectionListPath, exCategories[i], true, &exCount);
  275. printf("loaded category: %s\n", exCategories[i]);
  276. for (int x = 0; x < exCount - 1; x++)
  277. TextAppend(mkTextUpdated + mkIndex, TextFormat(" %s/%s \\\n", exCatList[x].category, exCatList[x].name), &mkIndex);
  278. TextAppend(mkTextUpdated + mkIndex, TextFormat(" %s/%s\n\n", exCatList[exCount - 1].category, exCatList[exCount - 1].name), &mkIndex);
  279. UnloadExamplesData(exCatList);
  280. }
  281. // Add the remaining part of the original file
  282. TextAppend(mkTextUpdated + mkIndex, mkText + exListEndIndex, &mkIndex);
  283. // Save updated file
  284. SaveFileText(TextFormat("%s/Makefile", exBasePath), mkTextUpdated);
  285. UnloadFileText(mkText);
  286. RL_FREE(mkTextUpdated);
  287. //------------------------------------------------------------------------------------------------
  288. // Edit: raylib/examples/Makefile.Web --> Add new example
  289. //------------------------------------------------------------------------------------------------
  290. // TODO.
  291. //------------------------------------------------------------------------------------------------
  292. // Edit: raylib/examples/README.md --> Add new example
  293. //------------------------------------------------------------------------------------------------
  294. // TODO: Use [examples_list.txt] to update/regen README.md
  295. //------------------------------------------------------------------------------------------------
  296. // Create: raylib/projects/VS2022/examples/<category>_example_name.vcxproj
  297. //------------------------------------------------------------------------------------------------
  298. FileCopy(TextFormat("%s/../projects/VS2022/examples/core_basic_window.vcxproj", exBasePath),
  299. TextFormat("%s/../projects/VS2022/examples/%s.vcxproj", exBasePath, exName));
  300. FileTextReplace(TextFormat("%s/../projects/VS2022/examples/%s.vcxproj", exBasePath, exName),
  301. "core_basic_window", exName);
  302. FileTextReplace(TextFormat("%s/../projects/VS2022/examples/%s.vcxproj", exBasePath, exName),
  303. "..\\..\\examples\\core", TextFormat("..\\..\\examples\\%s", exCategory));
  304. // Edit: raylib/projects/VS2022/raylib.sln --> Add new example project
  305. system(TextFormat("dotnet solution raylib.sln add %s/../projects/VS2022/examples/%s.vcxproj", exBasePath, exName));
  306. //------------------------------------------------------------------------------------------------
  307. // Edit: raylib.com/common/examples.js --> Add new example
  308. // NOTE: Entries format: exampleEntry('⭐️☆☆☆' , 'core' , 'basic_window'),
  309. //------------------------------------------------------------------------------------------------
  310. char *jsText = LoadFileText(TextFormat("%s/../common/examples.js", exWebPath));
  311. char *jsTextUpdated = (char *)RL_CALLOC(2*1024*1024, 1); // Updated examples.js copy, 2MB
  312. exListStartIndex = TextFindIndex(jsText, "//EXAMPLE_DATA_LIST_START");
  313. exListEndIndex = TextFindIndex(jsText, "//EXAMPLE_DATA_LIST_END");
  314. int jsIndex = exListStartIndex;
  315. memcpy(jsTextUpdated, jsText, exListStartIndex);
  316. TextAppend(jsTextUpdated + jsIndex, "//EXAMPLE_DATA_LIST_START\n", &jsIndex);
  317. TextAppend(jsTextUpdated + jsIndex, "var exampleData = [\n", &jsIndex);
  318. // NOTE: We avoid "others" category
  319. for (int i = 0, exCount = 0; i < MAX_EXAMPLE_CATEGORIES - 1; i++)
  320. {
  321. rlExampleInfo *exCatList = LoadExamplesData(exCollectionListPath, exCategories[i], true, &exCount);
  322. for (int x = 0; x < exCount; x++)
  323. {
  324. //char stars[16] = { 0 };
  325. //for (int s = 0; s < 4; s++) strcpy(stars + 3)
  326. TextAppend(jsTextUpdated + mkIndex,
  327. TextFormat(" exampleEntry('%s%s%s%s' , '%s' , '%s'),\n",
  328. "⭐️", "☆", "☆", "☆",
  329. exCatList[x].category,
  330. exCatList[x].name + strlen(exCatList[x].category) + 1),
  331. &jsIndex);
  332. }
  333. UnloadExamplesData(exCatList);
  334. }
  335. // Add the remaining part of the original file
  336. TextAppend(jsTextUpdated + jsIndex, jsText + exListEndIndex, &jsIndex);
  337. // Save updated file
  338. SaveFileText(TextFormat("%s/Makefile", exBasePath), jsTextUpdated);
  339. UnloadFileText(jsText);
  340. RL_FREE(jsTextUpdated);
  341. //------------------------------------------------------------------------------------------------
  342. // Recompile example (on raylib side)
  343. // NOTE: Tools requirements: emscripten, w64devkit
  344. // Compile to: raylib.com/examples/<category>/<category>_example_name.html
  345. // Compile to: raylib.com/examples/<category>/<category>_example_name.data
  346. // Compile to: raylib.com/examples/<category>/<category>_example_name.wasm
  347. // Compile to: raylib.com/examples/<category>/<category>_example_name.js
  348. // TODO: WARNING: This .BAT is not portable and it does not consider RESOURCES for Web properly,
  349. // Makefile.Web should be used... but it requires proper editing first!
  350. system(TextFormat("%s/../build_example_web.bat %s/%s", exBasePath, exCategory, exName));
  351. // Copy results to web side
  352. FileCopy(TextFormat("%s/%s/%s.html", exBasePath, exCategory, exName),
  353. TextFormat("%s/%s/%s.html", exWebPath, exCategory, exName));
  354. FileCopy(TextFormat("%s/%s/%s.data", exBasePath, exCategory, exName),
  355. TextFormat("%s/%s/%s.data", exWebPath, exCategory, exName));
  356. FileCopy(TextFormat("%s/%s/%s.wasm", exBasePath, exCategory, exName),
  357. TextFormat("%s/%s/%s.wasm", exWebPath, exCategory, exName));
  358. FileCopy(TextFormat("%s/%s/%s.js", exBasePath, exCategory, exName),
  359. TextFormat("%s/%s/%s.js", exWebPath, exCategory, exName));
  360. } break;
  361. case 3: // Rename
  362. {
  363. // Rename all required files
  364. rename(TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName),
  365. TextFormat("%s/%s/%s.c", exBasePath, exCategory, exRename));
  366. rename(TextFormat("%s/%s/%s.png", exBasePath, exCategory, exName),
  367. TextFormat("%s/%s/%s.png", exBasePath, exCategory, exRename));
  368. FileTextReplace(TextFormat("%s/Makefile", exBasePath), exName, exRename);
  369. FileTextReplace(TextFormat("%s/Makefile.Web", exBasePath), exName, exRename);
  370. FileTextReplace(TextFormat("%s/README.md", exBasePath), exName, exRename);
  371. rename(TextFormat("%s/../projects/VS2022/examples/%s.vcxproj", exBasePath, exName),
  372. TextFormat("%s/../projects/VS2022/examples/%s.vcxproj", exBasePath, exRename));
  373. FileTextReplace(TextFormat("%s/../projects/VS2022/raylib.sln", exBasePath), exName, exRename);
  374. // Remove old web compilation
  375. FileTextReplace(TextFormat("%s/../common/examples.js", exWebPath), exName, exRename);
  376. remove(TextFormat("%s/%s/%s.html", exWebPath, exCategory, exName));
  377. remove(TextFormat("%s/%s/%s.data", exWebPath, exCategory, exName));
  378. remove(TextFormat("%s/%s/%s.wasm", exWebPath, exCategory, exName));
  379. remove(TextFormat("%s/%s/%s.js", exWebPath, exCategory, exName));
  380. // Recompile example (on raylib side)
  381. // NOTE: Tools requirements: emscripten, w64devkit
  382. system(TextFormat("%s/../build_example_web.bat %s/%s", exBasePath, exCategory, exName));
  383. // Copy results to web side
  384. FileCopy(TextFormat("%s/%s/%s.html", exBasePath, exCategory, exName),
  385. TextFormat("%s/%s/%s.html", exWebPath, exCategory, exName));
  386. FileCopy(TextFormat("%s/%s/%s.data", exBasePath, exCategory, exName),
  387. TextFormat("%s/%s/%s.data", exWebPath, exCategory, exName));
  388. FileCopy(TextFormat("%s/%s/%s.wasm", exBasePath, exCategory, exName),
  389. TextFormat("%s/%s/%s.wasm", exWebPath, exCategory, exName));
  390. FileCopy(TextFormat("%s/%s/%s.js", exBasePath, exCategory, exName),
  391. TextFormat("%s/%s/%s.js", exWebPath, exCategory, exName));
  392. } break;
  393. case 4: // Remove
  394. {
  395. // TODO: Remove and update all required files...
  396. } break;
  397. case 5: // Validate
  398. {
  399. // TODO: Validate examples collection against [examples_list.txt]
  400. // Validate: raylib/examples/<category>/<category>_example_name.c
  401. // Validate: raylib/examples/<category>/<category>_example_name.png
  402. // Validate: raylib/examples/<category>/resources/.. -> Not possible for now...
  403. // Validate: raylib/examples/Makefile
  404. // Validate: raylib/examples/Makefile.Web
  405. // Validate: raylib/examples/README.md
  406. // Validate: raylib/projects/VS2022/examples/<category>_example_name.vcxproj
  407. // Validate: raylib/projects/VS2022/raylib.sln
  408. // Validate: raylib.com/common/examples.js
  409. // Validate: raylib.com/examples/<category>/<category>_example_name.html
  410. // Validate: raylib.com/examples/<category>/<category>_example_name.data
  411. // Validate: raylib.com/examples/<category>/<category>_example_name.wasm
  412. // Validate: raylib.com/examples/<category>/<category>_example_name.js
  413. } break;
  414. default: // Help
  415. {
  416. // Supported commands:
  417. // help : Provides command-line usage information
  418. // create <new_example_name> : Creates an empty example, from internal template
  419. // add <example_name> : Add existing example, category extracted from name
  420. // rename <old_examples_name> <new_example_name> : Rename an existing example
  421. // remove <example_name> : Remove an existing example
  422. printf("\n////////////////////////////////////////////////////////////////////////////////////////////\n");
  423. printf("// //\n");
  424. printf("// rexm [raylib examples manager] - A simple command-line tool to manage raylib examples //\n");
  425. printf("// powered by raylib v5.6-dev //\n");
  426. printf("// //\n");
  427. printf("// Copyright (c) 2025 Ramon Santamaria (@raysan5) //\n");
  428. printf("// //\n");
  429. printf("////////////////////////////////////////////////////////////////////////////////////////////\n\n");
  430. printf("USAGE:\n\n");
  431. printf(" > rexm help|create|add|rename|remove <example_name> [<example_rename>]\n");
  432. printf("\nOPTIONS:\n\n");
  433. printf(" help : Provides command-line usage information\n");
  434. printf(" create <new_example_name> : Creates an empty example, from internal template\n");
  435. printf(" add <example_name> : Add existing example, category extracted from name\n");
  436. printf(" Supported categories: core, shapes, textures, text, models\n");
  437. printf(" rename <old_examples_name> <new_example_name> : Rename an existing example\n");
  438. printf(" remove <example_name> : Remove an existing example\n\n");
  439. printf("\nEXAMPLES:\n\n");
  440. printf(" > rexm add shapes_custom_stars\n");
  441. printf(" Add and updates new example provided <shapes_custom_stars>\n\n");
  442. printf(" > rexm rename core_basic_window core_cool_window\n");
  443. printf(" Renames and updates example <core_basic_window> to <core_cool_window>\n\n");
  444. } break;
  445. }
  446. return 0;
  447. }
  448. //----------------------------------------------------------------------------------
  449. // Module specific functions definition
  450. //----------------------------------------------------------------------------------
  451. // Load examples collection information
  452. static rlExampleInfo *LoadExamplesData(const char *fileName, const char *category, bool sort, int *exCount)
  453. {
  454. #define MAX_EXAMPLES_INFO 256
  455. rlExampleInfo *exInfo = (rlExampleInfo *)RL_CALLOC(MAX_EXAMPLES_INFO, sizeof(rlExampleInfo));
  456. int exCounter = 0;
  457. char *text = LoadFileText(fileName);
  458. if (text != NULL)
  459. {
  460. int lineCount = 0;
  461. const char **linePtrs = GetTextLines(text, &lineCount);
  462. for (int i = 0; i < lineCount; i++)
  463. {
  464. // Basic validation for lines start categories
  465. if ((linePtrs[i][0] != '#') &&
  466. ((linePtrs[i][0] == 'c') || // core
  467. (linePtrs[i][0] == 's') || // shapes, shaders
  468. (linePtrs[i][0] == 't') || // textures, text
  469. (linePtrs[i][0] == 'm') || // models
  470. (linePtrs[i][0] == 'a') || // audio
  471. (linePtrs[i][0] == 'o'))) // others
  472. {
  473. rlExampleInfo info = { 0 };
  474. int result = ParseExampleInfoLine(linePtrs[i], &info);
  475. if (result == 1) // Success on parsing
  476. {
  477. if (strcmp(category, "ALL") == 0)
  478. {
  479. // Add all examples to the list
  480. memcpy(&exInfo[exCounter], &info, sizeof(rlExampleInfo));
  481. exCounter++;
  482. }
  483. else if (strcmp(info.category, category) == 0)
  484. {
  485. // Get only specific category examples
  486. memcpy(&exInfo[exCounter], &info, sizeof(rlExampleInfo));
  487. exCounter++;
  488. }
  489. }
  490. }
  491. }
  492. UnloadFileText(text);
  493. }
  494. // Sorting required
  495. if (sort) SortExampleByName(exInfo, exCounter);
  496. *exCount = exCounter;
  497. return exInfo;
  498. }
  499. // Unload examples collection data
  500. static void UnloadExamplesData(rlExampleInfo *exInfo)
  501. {
  502. RL_FREE(exInfo);
  503. }
  504. // Replace text in an existing file
  505. static int FileTextReplace(const char *fileName, const char *textLookUp, const char *textReplace)
  506. {
  507. int result = 0;
  508. char *fileText = NULL;
  509. char *fileTextUpdated = { 0 };
  510. if (FileExists(fileName))
  511. {
  512. fileText = LoadFileText(fileName);
  513. fileTextUpdated = TextReplace(fileText, textLookUp, textReplace);
  514. result = SaveFileText(fileName, fileTextUpdated);
  515. MemFree(fileTextUpdated);
  516. UnloadFileText(fileText);
  517. }
  518. return result;
  519. }
  520. // Copy file from one path to another
  521. // WARNING: Destination path must exist
  522. static int FileCopy(const char *srcPath, const char *dstPath)
  523. {
  524. int result = 0;
  525. int srcDataSize = 0;
  526. unsigned char *srcFileData = LoadFileData(srcPath, &srcDataSize);
  527. // TODO: Create required paths if they do not exist
  528. if ((srcFileData != NULL) && (srcDataSize > 0)) result = SaveFileData(dstPath, srcFileData, srcDataSize);
  529. UnloadFileData(srcFileData);
  530. return result;
  531. }
  532. // Rename file (if exists)
  533. // NOTE: Only rename file name required, not full path
  534. static int FileRename(const char *fileName, const char *fileRename)
  535. {
  536. int result = 0;
  537. if (FileExists(fileName)) rename(fileName, TextFormat("%s/%s", GetDirectoryPath(fileName), fileRename));
  538. return result;
  539. }
  540. // Remove file (if exists)
  541. static int FileRemove(const char *fileName)
  542. {
  543. int result = 0;
  544. if (FileExists(fileName)) remove(fileName);
  545. return result;
  546. }
  547. // Get text lines (by line-breaks '\n')
  548. // WARNING: It does not copy text data, just returns line pointers
  549. static const char **GetTextLines(const char *text, int *count)
  550. {
  551. #define MAX_TEXT_LINE_PTRS 128
  552. static const char *linePtrs[MAX_TEXT_LINE_PTRS] = { 0 };
  553. for (int i = 0; i < MAX_TEXT_LINE_PTRS; i++) linePtrs[i] = NULL; // Init NULL pointers to substrings
  554. int textSize = (int)strlen(text);
  555. linePtrs[0] = text;
  556. int len = 0;
  557. *count = 1;
  558. for (int i = 0, k = 0; (i < textSize) && (*count < MAX_TEXT_LINE_PTRS); i++)
  559. {
  560. if (text[i] == '\n')
  561. {
  562. k++;
  563. linePtrs[k] = &text[i + 1]; // WARNING: next value is valid?
  564. len = 0;
  565. *count += 1;
  566. }
  567. else len++;
  568. }
  569. return linePtrs;
  570. }
  571. // raylib example line info parser
  572. // Parses following line format: core;core_basic_window;⭐️☆☆☆;1.0;1.0;"Ray";@raysan5
  573. static int ParseExampleInfoLine(const char *line, rlExampleInfo *entry)
  574. {
  575. #define MAX_EXAMPLE_INFO_LINE_LEN 512
  576. char temp[MAX_EXAMPLE_INFO_LINE_LEN] = { 0 };
  577. strncpy(temp, line, MAX_EXAMPLE_INFO_LINE_LEN); // WARNING: Copy is needed because strtok() modifies string, adds '\0'
  578. temp[MAX_EXAMPLE_INFO_LINE_LEN - 1] = '\0'; // Ensure null termination
  579. int tokenCount = 0;
  580. char **tokens = TextSplit(line, ';', &tokenCount);
  581. // Get category and name
  582. strcpy(entry->category, tokens[0]);
  583. strcpy(entry->name, tokens[1]);
  584. // Parsing stars
  585. // NOTE: Counting the unicode char occurrences: ⭐️
  586. const char *ptr = tokens[2];
  587. while (*ptr)
  588. {
  589. if (((unsigned char)ptr[0] == 0xE2) &&
  590. ((unsigned char)ptr[1] == 0xAD) &&
  591. ((unsigned char)ptr[2] == 0x90))
  592. {
  593. entry->stars++;
  594. ptr += 3; // Advance past multibyte character
  595. }
  596. else ptr++;
  597. }
  598. // Get raylib creation/update versions
  599. entry->verCreated = strtof(tokens[3], NULL);
  600. entry->verUpdated = strtof(tokens[4], NULL);
  601. // Get author and github
  602. char *quote1 = strchr(tokens[5], '"');
  603. char *quote2 = quote1? strchr(quote1 + 1, '"') : NULL;
  604. if (quote1 && quote2) strcpy(entry->author, quote1 + 1);
  605. strcpy(entry->authorGitHub, tokens[6]);
  606. return 1;
  607. }
  608. // Text compare, required for qsort() function
  609. static int rlExampleInfoCompare(const void *a, const void *b)
  610. {
  611. const rlExampleInfo *ex1 = (const rlExampleInfo *)a;
  612. const rlExampleInfo *ex2 = (const rlExampleInfo *)b;
  613. return strcmp(ex1->name, ex2->name);
  614. }
  615. // Sort array of strings by name
  616. // WARNING: items[] pointers are reorganized
  617. static void SortExampleByName(rlExampleInfo *items, int count)
  618. {
  619. qsort(items, count, sizeof(rlExampleInfo), rlExampleInfoCompare);
  620. }