git subrepo clone https://github.com/libretro/libretro-common.git deps/libretro-common
[pcsx_rearmed.git] / deps / libretro-common / samples / core_options / example_translation / translation scripts / intl / v1_to_v2_converter.py
CommitLineData
3719602c
PC
1#!/usr/bin/env python3
2
3"""Core options v1 to v2 converter
4
5Just run this script as follows, to convert 'libretro_core_options.h' & 'Libretro_coreoptions_intl.h' to v2:
6python3 "/path/to/v1_to_v2_converter.py" "/path/to/where/libretro_core_options.h & Libretro_coreoptions_intl.h/are"
7
8The original files will be preserved as *.v1
9"""
10import core_option_regex as cor
11import os
12import sys
13
14
15def create_v2_code_file(struct_text, file_name):
16 def replace_option(option_match):
17 _offset = option_match.start(0)
18
19 if option_match.group(3):
20 res = option_match.group(0)[:option_match.end(2) - _offset] + ',\n NULL' + \
21 option_match.group(0)[option_match.end(2) - _offset:option_match.end(3) - _offset] + \
22 'NULL,\n NULL,\n ' + option_match.group(0)[option_match.end(3) - _offset:]
23 else:
24 return option_match.group(0)
25
26 return res
27
28 comment_v1 = '/*\n' \
29 ' ********************************\n' \
30 ' * VERSION: 1.3\n' \
31 ' ********************************\n' \
32 ' *\n' \
33 ' * - 1.3: Move translations to libretro_core_options_intl.h\n' \
34 ' * - libretro_core_options_intl.h includes BOM and utf-8\n' \
35 ' * fix for MSVC 2010-2013\n' \
36 ' * - Added HAVE_NO_LANGEXTRA flag to disable translations\n' \
37 ' * on platforms/compilers without BOM support\n' \
38 ' * - 1.2: Use core options v1 interface when\n' \
39 ' * RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION is >= 1\n' \
40 ' * (previously required RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION == 1)\n' \
41 ' * - 1.1: Support generation of core options v0 retro_core_option_value\n' \
42 ' * arrays containing options with a single value\n' \
43 ' * - 1.0: First commit\n' \
44 '*/\n'
45
46 comment_v2 = '/*\n' \
47 ' ********************************\n' \
48 ' * VERSION: 2.0\n' \
49 ' ********************************\n' \
50 ' *\n' \
51 ' * - 2.0: Add support for core options v2 interface\n' \
52 ' * - 1.3: Move translations to libretro_core_options_intl.h\n' \
53 ' * - libretro_core_options_intl.h includes BOM and utf-8\n' \
54 ' * fix for MSVC 2010-2013\n' \
55 ' * - Added HAVE_NO_LANGEXTRA flag to disable translations\n' \
56 ' * on platforms/compilers without BOM support\n' \
57 ' * - 1.2: Use core options v1 interface when\n' \
58 ' * RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION is >= 1\n' \
59 ' * (previously required RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION == 1)\n' \
60 ' * - 1.1: Support generation of core options v0 retro_core_option_value\n' \
61 ' * arrays containing options with a single value\n' \
62 ' * - 1.0: First commit\n' \
63 '*/\n'
64
65 p_intl = cor.p_intl
66 p_set = cor.p_set
67 new_set = 'static INLINE void libretro_set_core_options(retro_environment_t environ_cb,\n' \
68 ' bool *categories_supported)\n' \
69 '{\n' \
70 ' unsigned version = 0;\n' \
71 '#ifndef HAVE_NO_LANGEXTRA\n' \
72 ' unsigned language = 0;\n' \
73 '#endif\n' \
74 '\n' \
75 ' if (!environ_cb || !categories_supported)\n' \
76 ' return;\n' \
77 '\n' \
78 ' *categories_supported = false;\n' \
79 '\n' \
80 ' if (!environ_cb(RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION, &version))\n' \
81 ' version = 0;\n' \
82 '\n' \
83 ' if (version >= 2)\n' \
84 ' {\n' \
85 '#ifndef HAVE_NO_LANGEXTRA\n' \
86 ' struct retro_core_options_v2_intl core_options_intl;\n' \
87 '\n' \
88 ' core_options_intl.us = &options_us;\n' \
89 ' core_options_intl.local = NULL;\n' \
90 '\n' \
91 ' if (environ_cb(RETRO_ENVIRONMENT_GET_LANGUAGE, &language) &&\n' \
92 ' (language < RETRO_LANGUAGE_LAST) && (language != RETRO_LANGUAGE_ENGLISH))\n' \
93 ' core_options_intl.local = options_intl[language];\n' \
94 '\n' \
95 ' *categories_supported = environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL,\n' \
96 ' &core_options_intl);\n' \
97 '#else\n' \
98 ' *categories_supported = environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2,\n' \
99 ' &options_us);\n' \
100 '#endif\n' \
101 ' }\n' \
102 ' else\n' \
103 ' {\n' \
104 ' size_t i, j;\n' \
105 ' size_t option_index = 0;\n' \
106 ' size_t num_options = 0;\n' \
107 ' struct retro_core_option_definition\n' \
108 ' *option_v1_defs_us = NULL;\n' \
109 '#ifndef HAVE_NO_LANGEXTRA\n' \
110 ' size_t num_options_intl = 0;\n' \
111 ' struct retro_core_option_v2_definition\n' \
112 ' *option_defs_intl = NULL;\n' \
113 ' struct retro_core_option_definition\n' \
114 ' *option_v1_defs_intl = NULL;\n' \
115 ' struct retro_core_options_intl\n' \
116 ' core_options_v1_intl;\n' \
117 '#endif\n' \
118 ' struct retro_variable *variables = NULL;\n' \
119 ' char **values_buf = NULL;\n' \
120 '\n' \
121 ' /* Determine total number of options */\n' \
122 ' while (true)\n' \
123 ' {\n' \
124 ' if (option_defs_us[num_options].key)\n' \
125 ' num_options++;\n' \
126 ' else\n' \
127 ' break;\n' \
128 ' }\n' \
129 '\n' \
130 ' if (version >= 1)\n' \
131 ' {\n' \
132 ' /* Allocate US array */\n' \
133 ' option_v1_defs_us = (struct retro_core_option_definition *)\n' \
134 ' calloc(num_options + 1, sizeof(struct retro_core_option_definition));\n' \
135 '\n' \
136 ' /* Copy parameters from option_defs_us array */\n' \
137 ' for (i = 0; i < num_options; i++)\n' \
138 ' {\n' \
139 ' struct retro_core_option_v2_definition *option_def_us = &option_defs_us[i];\n' \
140 ' struct retro_core_option_value *option_values = option_def_us->values;\n' \
141 ' struct retro_core_option_definition *option_v1_def_us = &option_v1_defs_us[i];\n' \
142 ' struct retro_core_option_value *option_v1_values = option_v1_def_us->values;\n' \
143 '\n' \
144 ' option_v1_def_us->key = option_def_us->key;\n' \
145 ' option_v1_def_us->desc = option_def_us->desc;\n' \
146 ' option_v1_def_us->info = option_def_us->info;\n' \
147 ' option_v1_def_us->default_value = option_def_us->default_value;\n' \
148 '\n' \
149 ' /* Values must be copied individually... */\n' \
150 ' while (option_values->value)\n' \
151 ' {\n' \
152 ' option_v1_values->value = option_values->value;\n' \
153 ' option_v1_values->label = option_values->label;\n' \
154 '\n' \
155 ' option_values++;\n' \
156 ' option_v1_values++;\n' \
157 ' }\n' \
158 ' }\n' \
159 '\n' \
160 '#ifndef HAVE_NO_LANGEXTRA\n' \
161 ' if (environ_cb(RETRO_ENVIRONMENT_GET_LANGUAGE, &language) &&\n' \
162 ' (language < RETRO_LANGUAGE_LAST) && (language != RETRO_LANGUAGE_ENGLISH) &&\n' \
163 ' options_intl[language])\n' \
164 ' option_defs_intl = options_intl[language]->definitions;\n' \
165 '\n' \
166 ' if (option_defs_intl)\n' \
167 ' {\n' \
168 ' /* Determine number of intl options */\n' \
169 ' while (true)\n' \
170 ' {\n' \
171 ' if (option_defs_intl[num_options_intl].key)\n' \
172 ' num_options_intl++;\n' \
173 ' else\n' \
174 ' break;\n' \
175 ' }\n' \
176 '\n' \
177 ' /* Allocate intl array */\n' \
178 ' option_v1_defs_intl = (struct retro_core_option_definition *)\n' \
179 ' calloc(num_options_intl + 1, sizeof(struct retro_core_option_definition));\n' \
180 '\n' \
181 ' /* Copy parameters from option_defs_intl array */\n' \
182 ' for (i = 0; i < num_options_intl; i++)\n' \
183 ' {\n' \
184 ' struct retro_core_option_v2_definition *option_def_intl = &option_defs_intl[i];\n' \
185 ' struct retro_core_option_value *option_values = option_def_intl->values;\n' \
186 ' struct retro_core_option_definition *option_v1_def_intl = &option_v1_defs_intl[i];\n' \
187 ' struct retro_core_option_value *option_v1_values = option_v1_def_intl->values;\n' \
188 '\n' \
189 ' option_v1_def_intl->key = option_def_intl->key;\n' \
190 ' option_v1_def_intl->desc = option_def_intl->desc;\n' \
191 ' option_v1_def_intl->info = option_def_intl->info;\n' \
192 ' option_v1_def_intl->default_value = option_def_intl->default_value;\n' \
193 '\n' \
194 ' /* Values must be copied individually... */\n' \
195 ' while (option_values->value)\n' \
196 ' {\n' \
197 ' option_v1_values->value = option_values->value;\n' \
198 ' option_v1_values->label = option_values->label;\n' \
199 '\n' \
200 ' option_values++;\n' \
201 ' option_v1_values++;\n' \
202 ' }\n' \
203 ' }\n' \
204 ' }\n' \
205 '\n' \
206 ' core_options_v1_intl.us = option_v1_defs_us;\n' \
207 ' core_options_v1_intl.local = option_v1_defs_intl;\n' \
208 '\n' \
209 ' environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL, &core_options_v1_intl);\n' \
210 '#else\n' \
211 ' environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS, option_v1_defs_us);\n' \
212 '#endif\n' \
213 ' }\n' \
214 ' else\n' \
215 ' {\n' \
216 ' /* Allocate arrays */\n' \
217 ' variables = (struct retro_variable *)calloc(num_options + 1,\n' \
218 ' sizeof(struct retro_variable));\n' \
219 ' values_buf = (char **)calloc(num_options, sizeof(char *));\n' \
220 '\n' \
221 ' if (!variables || !values_buf)\n' \
222 ' goto error;\n' \
223 '\n' \
224 ' /* Copy parameters from option_defs_us array */\n' \
225 ' for (i = 0; i < num_options; i++)\n' \
226 ' {\n' \
227 ' const char *key = option_defs_us[i].key;\n' \
228 ' const char *desc = option_defs_us[i].desc;\n' \
229 ' const char *default_value = option_defs_us[i].default_value;\n' \
230 ' struct retro_core_option_value *values = option_defs_us[i].values;\n' \
231 ' size_t buf_len = 3;\n' \
232 ' size_t default_index = 0;\n' \
233 '\n' \
234 ' values_buf[i] = NULL;\n' \
235 '\n' \
236 ' if (desc)\n' \
237 ' {\n' \
238 ' size_t num_values = 0;\n' \
239 '\n' \
240 ' /* Determine number of values */\n' \
241 ' while (true)\n' \
242 ' {\n' \
243 ' if (values[num_values].value)\n' \
244 ' {\n' \
245 ' /* Check if this is the default value */\n' \
246 ' if (default_value)\n' \
247 ' if (strcmp(values[num_values].value, default_value) == 0)\n' \
248 ' default_index = num_values;\n' \
249 '\n' \
250 ' buf_len += strlen(values[num_values].value);\n' \
251 ' num_values++;\n' \
252 ' }\n' \
253 ' else\n' \
254 ' break;\n' \
255 ' }\n' \
256 '\n' \
257 ' /* Build values string */\n' \
258 ' if (num_values > 0)\n' \
259 ' {\n' \
260 ' buf_len += num_values - 1;\n' \
261 ' buf_len += strlen(desc);\n' \
262 '\n' \
263 ' values_buf[i] = (char *)calloc(buf_len, sizeof(char));\n' \
264 ' if (!values_buf[i])\n' \
265 ' goto error;\n' \
266 '\n' \
267 ' strcpy(values_buf[i], desc);\n' \
268 ' strcat(values_buf[i], "; ");\n' \
269 '\n' \
270 ' /* Default value goes first */\n' \
271 ' strcat(values_buf[i], values[default_index].value);\n' \
272 '\n' \
273 ' /* Add remaining values */\n' \
274 ' for (j = 0; j < num_values; j++)\n' \
275 ' {\n' \
276 ' if (j != default_index)\n' \
277 ' {\n' \
278 ' strcat(values_buf[i], "|");\n' \
279 ' strcat(values_buf[i], values[j].value);\n' \
280 ' }\n' \
281 ' }\n' \
282 ' }\n' \
283 ' }\n' \
284 '\n' \
285 ' variables[option_index].key = key;\n' \
286 ' variables[option_index].value = values_buf[i];\n' \
287 ' option_index++;\n' \
288 ' }\n' \
289 '\n' \
290 ' /* Set variables */\n' \
291 ' environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, variables);\n' \
292 ' }\n' \
293 '\n' \
294 'error:\n' \
295 ' /* Clean up */\n' \
296 '\n' \
297 ' if (option_v1_defs_us)\n' \
298 ' {\n' \
299 ' free(option_v1_defs_us);\n' \
300 ' option_v1_defs_us = NULL;\n' \
301 ' }\n' \
302 '\n' \
303 '#ifndef HAVE_NO_LANGEXTRA\n' \
304 ' if (option_v1_defs_intl)\n' \
305 ' {\n' \
306 ' free(option_v1_defs_intl);\n' \
307 ' option_v1_defs_intl = NULL;\n' \
308 ' }\n' \
309 '#endif\n' \
310 '\n' \
311 ' if (values_buf)\n' \
312 ' {\n' \
313 ' for (i = 0; i < num_options; i++)\n' \
314 ' {\n' \
315 ' if (values_buf[i])\n' \
316 ' {\n' \
317 ' free(values_buf[i]);\n' \
318 ' values_buf[i] = NULL;\n' \
319 ' }\n' \
320 ' }\n' \
321 '\n' \
322 ' free(values_buf);\n' \
323 ' values_buf = NULL;\n' \
324 ' }\n' \
325 '\n' \
326 ' if (variables)\n' \
327 ' {\n' \
328 ' free(variables);\n' \
329 ' variables = NULL;\n' \
330 ' }\n' \
331 ' }\n' \
332 '}\n' \
333 '\n' \
334 '#ifdef __cplusplus\n' \
335 '}\n' \
336 '#endif'
337
338 struct_groups = cor.p_struct.finditer(struct_text)
339 out_text = struct_text
340
341 for construct in struct_groups:
342 repl_text = ''
343 declaration = construct.group(1)
344 struct_match = cor.p_type_name.search(declaration)
345 if struct_match:
346 if struct_match.group(3):
347 struct_type_name_lang = struct_match.group(1, 2, 3)
348 declaration_end = declaration[struct_match.end(1):]
349 elif struct_match.group(4):
350 struct_type_name_lang = struct_match.group(1, 2, 4)
351 declaration_end = declaration[struct_match.end(1):]
352 else:
353 struct_type_name_lang = sum((struct_match.group(1, 2), ('_us',)), ())
354 declaration_end = f'{declaration[struct_match.end(1):struct_match.end(2)]}_us' \
355 f'{declaration[struct_match.end(2):]}'
356 else:
357 return -1
358
359 if 'retro_core_option_definition' == struct_type_name_lang[0]:
360 import shutil
361 shutil.copy(file_name, file_name + '.v1')
362 new_declaration = f'\nstruct retro_core_option_v2_category option_cats{struct_type_name_lang[2]}[] = ' \
363 '{\n { NULL, NULL, NULL },\n' \
364 '};\n\n' \
365 + declaration[:struct_match.start(1)] + \
366 'retro_core_option_v2_definition' \
367 + declaration_end
368 offset = construct.start(0)
369 repl_text = repl_text + cor.re.sub(cor.re.escape(declaration), new_declaration,
370 construct.group(0)[:construct.start(2) - offset])
371 content = construct.group(2)
372 new_content = cor.p_option.sub(replace_option, content)
373
374 repl_text = repl_text + new_content + cor.re.sub(r'{\s*NULL,\s*NULL,\s*NULL,\s*{\{0}},\s*NULL\s*},\s*};',
375 '{ NULL, NULL, NULL, NULL, NULL, NULL, {{0}}, NULL },\n};'
376 '\n\nstruct retro_core_options_v2 options' +
377 struct_type_name_lang[2] + ' = {\n'
378 f' option_cats{struct_type_name_lang[2]},\n'
379 f' option_defs{struct_type_name_lang[2]}\n'
380 '};',
381 construct.group(0)[construct.end(2) - offset:])
382 out_text = cor.re.sub(cor.re.escape(construct.group(0)), repl_text, out_text)
383 else:
384 return -2
385 with open(file_name, 'w', encoding='utf-8') as code_file:
386 out_text = cor.re.sub(cor.re.escape(comment_v1), comment_v2, out_text)
387 intl = p_intl.search(out_text)
388 if intl:
389 new_intl = out_text[:intl.start(1)] \
390 + 'struct retro_core_options_v2 *options_intl[RETRO_LANGUAGE_LAST]' \
391 + out_text[intl.end(1):intl.start(2)] \
392 + '&options_us, /* RETRO_LANGUAGE_ENGLISH */' \
393 ' &options_ja, /* RETRO_LANGUAGE_JAPANESE */' \
394 ' &options_fr, /* RETRO_LANGUAGE_FRENCH */' \
395 ' &options_es, /* RETRO_LANGUAGE_SPANISH */' \
396 ' &options_de, /* RETRO_LANGUAGE_GERMAN */' \
397 ' &options_it, /* RETRO_LANGUAGE_ITALIAN */' \
398 ' &options_nl, /* RETRO_LANGUAGE_DUTCH */' \
399 ' &options_pt_br, /* RETRO_LANGUAGE_PORTUGUESE_BRAZIL */' \
400 ' &options_pt_pt, /* RETRO_LANGUAGE_PORTUGUESE_PORTUGAL */' \
401 ' &options_ru, /* RETRO_LANGUAGE_RUSSIAN */' \
402 ' &options_ko, /* RETRO_LANGUAGE_KOREAN */' \
403 ' &options_cht, /* RETRO_LANGUAGE_CHINESE_TRADITIONAL */' \
404 ' &options_chs, /* RETRO_LANGUAGE_CHINESE_SIMPLIFIED */' \
405 ' &options_eo, /* RETRO_LANGUAGE_ESPERANTO */' \
406 ' &options_pl, /* RETRO_LANGUAGE_POLISH */' \
407 ' &options_vn, /* RETRO_LANGUAGE_VIETNAMESE */' \
408 ' &options_ar, /* RETRO_LANGUAGE_ARABIC */' \
409 ' &options_el, /* RETRO_LANGUAGE_GREEK */' \
410 ' &options_tr, /* RETRO_LANGUAGE_TURKISH */' \
411 ' &options_sv, /* RETRO_LANGUAGE_SLOVAK */' \
412 ' &options_fa, /* RETRO_LANGUAGE_PERSIAN */' \
413 ' &options_he, /* RETRO_LANGUAGE_HEBREW */' \
414 ' &options_ast, /* RETRO_LANGUAGE_ASTURIAN */' \
415 ' &options_fi, /* RETRO_LANGUAGE_FINNISH */' \
416 + out_text[intl.end(2):]
417 out_text = p_set.sub(new_set, new_intl)
418 else:
419 out_text = p_set.sub(new_set, out_text)
420 code_file.write(out_text)
421
422 return 1
423
424
425# -------------------- MAIN -------------------- #
426
427if __name__ == '__main__':
428 try:
429 if os.path.isfile(sys.argv[1]):
430 _temp = os.path.dirname(sys.argv[1])
431 else:
432 _temp = sys.argv[1]
433 while _temp.endswith('/') or _temp.endswith('\\'):
434 _temp = _temp[:-1]
435 DIR_PATH = _temp
436 except IndexError:
437 DIR_PATH = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
438 print("No path provided, assuming parent directory:\n" + DIR_PATH)
439
440 H_FILE_PATH = os.path.join(DIR_PATH, 'libretro_core_options.h')
441 INTL_FILE_PATH = os.path.join(DIR_PATH, 'libretro_core_options_intl.h')
442
443 for file in (H_FILE_PATH, INTL_FILE_PATH):
444 if os.path.isfile(file):
445 with open(file, 'r+', encoding='utf-8') as h_file:
446 text = h_file.read()
447 try:
448 test = create_v2_code_file(text, file)
449 except Exception as e:
450 print(e)
451 test = -1
452 if -1 > test:
453 print('Your file looks like it already is v2? (' + file + ')')
454 continue
455 if 0 > test:
456 print('An error occured! Please make sure to use the complete v1 struct! (' + file + ')')
457 continue
458 else:
459 print(file + ' not found.')