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
1 #!/usr/bin/env python3
2
3 """Core options v1 to v2 converter
4
5 Just run this script as follows, to convert 'libretro_core_options.h' & 'Libretro_coreoptions_intl.h' to v2:
6 python3 "/path/to/v1_to_v2_converter.py" "/path/to/where/libretro_core_options.h & Libretro_coreoptions_intl.h/are"
7
8 The original files will be preserved as *.v1
9 """
10 import core_option_regex as cor
11 import os
12 import sys
13
14
15 def 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
427 if __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.')