You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

317 lines
9.6 KiB

#!/bin/python
from __future__ import print_function # python >= 2.6, chained 'with' >= 2.7
from os.path import dirname, abspath
from os import fdopen as os_fdopen, remove as os_remove, name as os_name
from shutil import copy2
from subprocess import Popen, PIPE
from sys import exit as sys_exit, stderr
from tempfile import mkstemp
from contextlib import contextmanager
from threading import Timer
import re
ROOT_DIR = dirname(dirname(abspath(__file__)))
# ==============================================================================
FILE_BINDINGS = "%s/src/uncrustify_emscripten.cpp" % ROOT_DIR
FILE_TS = "%s/emscripten/libUncrustify.d.ts" % ROOT_DIR
REGION_START = "region enum bindings"
REGION_END = "endregion enum bindings"
''' Enums which values need to be updated in the binding code '''
ENUMS_INFO = [
{
'name': 'option_type_e',
'substitute_name': 'OptionType',
'filepath': '%s/src/option.h' % ROOT_DIR,
'extra_arg': [],
'filter_values': [],
'suffix_chars': 0,
},
{
'name': 'iarf_e',
'substitute_name': 'IARF',
'filepath': '%s/src/option.h' % ROOT_DIR,
'extra_arg': [],
'filter_values': ['NOT_DEFINED'],
'suffix_chars': 0,
},
{
'name': 'line_end_e',
'substitute_name': 'LineEnd',
'filepath': '%s/src/option.h' % ROOT_DIR,
'extra_arg': [],
'filter_values': [],
'suffix_chars': 0,
},
{
'name': 'token_pos_e',
'substitute_name': 'TokenPos',
'filepath': '%s/src/option.h' % ROOT_DIR,
'extra_arg': [],
'filter_values': [],
'suffix_chars': 0,
},
{
'name': 'log_sev_t',
'substitute_name': 'LogType',
'filepath': '%s/src/log_levels.h' % ROOT_DIR,
'extra_arg': [],
'filter_values': [],
'suffix_chars': 1,
},
{
'name': 'c_token_t',
'substitute_name': 'TokenType',
'filepath': '%s/src/token_enum.h' % ROOT_DIR,
'extra_arg': [],
'filter_values': ['CT_TOKEN_COUNT_'],
'suffix_chars': 3,
},
{
'name': 'lang_flag_e',
'substitute_name': 'Language',
'filepath': '%s/src/uncrustify_types.h' % ROOT_DIR,
'extra_arg': ["-extra-arg=-std=c++1z", "-extra-arg=-DEMSCRIPTEN"],
'filter_values': [
'LANG_ALLC',
'LANG_ALL',
'FLAG_HDR',
'FLAG_DIG',
'FLAG_PP',
],
'suffix_chars': 5,
},
]
# ==============================================================================
NULL_DEV = "/dev/null" if os_name != "nt" else "nul"
@contextmanager
def make_raw_temp_file(*args, **kwargs):
fd, tmp_file_name = mkstemp(*args, **kwargs)
try:
yield (fd, tmp_file_name)
finally:
os_remove(tmp_file_name)
@contextmanager
def open_fd(*args, **kwargs):
fp = os_fdopen(*args, **kwargs)
try:
yield fp
finally:
fp.close()
def term_proc(proc, timeout):
"""
helper function terminate a process if a timer times out
:param proc: the process object that is going to be terminated
:param timeout: value that will be set to indicate termination
"""
timeout["value"] = True
proc.terminate()
def proc_output(args, timeout_sec=10):
"""
grabs output from called program
:param args: string array containing program name and program arguments
:param timeout_sec: max sec the program can run without being terminated
:return: utf8 decoded program output in a string
"""
proc = Popen(args, stdout=PIPE)
timeout = {"value": False}
if timeout_sec is not None:
timeout = {"value": False}
timer = Timer(timeout_sec, term_proc, [proc, timeout])
timer.start()
output_b, error_txt_b = proc.communicate()
if timeout_sec is not None:
timer.cancel()
output = output_b.decode("UTF-8")
if timeout["value"]:
print("proc timeout: %s" % ' '.join(args), file=stderr)
return output if not timeout["value"] else None
def get_enum_lines(enum_info):
"""
extracts enum values from a file via clang-check
:param enum_info: dict with:
'name' (name of the enum),
'filepath' (file containing the enum definition),
'extra_arg' (extra arguments passed to clang-check)
:return: list containing enum values
"""
cut_len = len(enum_info['name'])
proc_args = ["clang-check", enum_info['filepath'], "-ast-dump",
'-ast-dump-filter=%s' % enum_info['name']]
proc_args += enum_info['extra_arg']
output = proc_output(proc_args)
if output is None or len(output) == 0:
print("ScriptError: %s - empty clang-check return" % get_enum_lines.__name__,
file=stderr)
return ()
reg_obj = re.compile("EnumConstantDecl.+col:\d+ (referenced )?(\w+)")
lines = [m.group(2) for l in output.splitlines()
for m in [re.search(reg_obj, l)] if m]
lines = [line for line in lines if line not in enum_info['filter_values']]
if len(lines) == 0:
print("ScriptError: %s - no enum_info names found" % get_enum_lines.__name__,
file=stderr)
return ()
return lines
def write_ts(opened_file_obj, enum_info):
"""
writes enum values in a specific typescript d.ts file format
:param opened_file_obj: opened file file object (with write permissions)
:param enum_info: dict with:
'name' (name of the enum),
'substitute_name' (substitute name for the enum),
'filepath' (file containing the enum definition),
'extra_arg' (extra arguments passed to clang-check)
:return: False on failure else True
"""
lines = get_enum_lines(enum_info)
if len(lines) == 0:
return False
opened_file_obj.write(
' export interface %sValue extends EmscriptenEnumTypeObject {}\n'
' export interface %s extends EmscriptenEnumType\n'
' {\n'
% (enum_info['substitute_name'], enum_info['substitute_name'])
)
for line in lines:
opened_file_obj.write(
' %s : %sValue;\n'
% (line[enum_info['suffix_chars']:], enum_info['substitute_name'])
)
opened_file_obj.write(
' }\n\n'
)
return True
def write_bindings(opened_file_obj, enum_info):
"""
writes enum values in a specific emscripten embind enum bindings format
:param opened_file_obj: opened file file object (with write permissions)
:param enum_info: dict with:
'name' (name of the enum),
'filepath' (file containing the enum definition),
'extra_arg' (extra arguments passed to clang-check)
:return: False on failure else True
"""
lines = get_enum_lines(enum_info)
if len(lines) == 0:
return False
opened_file_obj.write(
' enum_<%s>("%s")' % (enum_info['name'], enum_info['substitute_name'])
)
for line in lines:
opened_file_obj.write(
'\n .value("%s", %s::%s)'
% (line[enum_info['suffix_chars']:], enum_info['name'], line)
)
opened_file_obj.write(
';\n\n'
)
return True
def update_file(file_path, writer_func, enums_info):
"""
reads in a file and replaces old enum value in a region, which is defined by
region start and end string, with updated ones
:param file_path: file in which the replacement will be made
:param writer_func: name of the function that will be called to write new
content
:param enums_info:list of dicts each containing:
'name' (name of the enum),
'substitute_name' (substitute name for the enum),
'filepath' (file containing the enum definition),
'extra_arg' (extra arguments passed to clang-check)
:return: False on failure else True
"""
in_target_region = False
reg_obj_start = re.compile(".*%s$" % REGION_START)
reg_obj_end = re.compile(".*%s$" % REGION_END)
reg_obj = reg_obj_start
with make_raw_temp_file(suffix='.unc') as (fd, tmp_file_path):
with open(file_path, 'r') as fr, open_fd(fd, 'w') as fw:
for line in fr:
match = None if reg_obj is None else re.search(reg_obj, line)
if match is None and not in_target_region:
fw.write(line) # write out of region code
elif match is not None and not in_target_region:
fw.write(line) # hit the start region
in_target_region = True
reg_obj = reg_obj_end
for enum in enums_info:
succes_flag = writer_func(fw, enum)
if not succes_flag: # abort, keep input file clean
return False
elif match is None and in_target_region:
pass # ignore old binding code
elif match and in_target_region: # hit the endregion
fw.write(line)
in_target_region = False
reg_obj = None
copy2(tmp_file_path, file_path) # overwrite input file
return True
def main():
flag = update_file(FILE_BINDINGS, write_bindings, ENUMS_INFO)
if not flag:
return 1
flag = update_file(FILE_TS, write_ts, ENUMS_INFO)
if not flag:
return 1
return 0
if __name__ == "__main__":
sys_exit(main())