import os import shutil import re import stat from tempfile import mkstemp friend_regex = re.compile('friend', re.I) template_regex = re.compile('^(\s*template)(.*)', re.I) class_regex = re.compile('class', re.I) struct_regex = re.compile('struct', re.I) comment_regex = re.compile('^(\s*\/\/)(.*)') right_side_comments_regex = re.compile('(\/\/.*)') include_regex = re.compile('^((?!(^\#include\s*<)).)*$') cpp_keywords_regex = re.compile('.*(std::unique_ptr<.*>|static_cast<.*>|const_cast<.*>|reinterpret_cast<.*>).*$') angular_content_pattern = '([^<>]*)' angular_content_regex = re.compile('(?<=<)' + angular_content_pattern + '(?=>)') angular_pattern = '<' + angular_content_pattern + '>' variable_pattern = '([a-zA-Z_][a-zA-Z0-9]+)' variable_template_pattern = variable_pattern + '\s*' + angular_pattern variable_template_regex = re.compile(variable_template_pattern) ubiservices_root = os.path.join(os.getcwd(), '..') source_path = os.path.join(ubiservices_root, 'client-sdk/public/ubiservices') target_path = os.path.join(ubiservices_root, 'doc/temp_headers/ubiservices') global_transformed_templates_list = [] def force_remove(action, file_path, exception): remove_readonly(file_path) def remove_readonly(file_path): os.chmod(file_path, stat.S_IWRITE) if (os.path.isdir(file_path)): os.rmdir(file_path) else: os.remove(file_path) def copy_headers(): if os.path.exists(target_path): shutil.rmtree(target_path, onerror=force_remove) shutil.copytree(source_path, target_path) def make_name(*strings): #remove the pointers/references from the types strings = [re.sub('[\^\&\*]','',w) for w in strings] result = '_'.join(map(str,strings)) result = re.sub('::', '_', result) if(result[0] == '_'): return '' return result def get_transformed_template(s): name = '' while re.search(variable_template_pattern, s): name = compose_transformed_template(s) s = replace_template(s) return name # this method handles patterns such as: # input: Map # output: sdk_Map_UserId_ProfileInfo def replace_template(s): result = variable_template_regex.sub(compose_transformed_template(s), s, 1) result = re.sub('[\^\&\*]', '', result) return result def compose_transformed_template(s): components = get_transformed_template_components(s) return make_name(components[0], components[1]) # this method is used to obtain the template components found in the input # input: Map # output: (Map, String_int) def get_transformed_template_components(s): result = '' match_string = variable_template_regex.search(s).group() var1 = re.sub(' ', '', re.search('[^<]*', match_string).group()) tp = angular_content_regex.search(match_string).group() tp = re.sub('\s', '', re.sub(',', '_', tp)) return var1, tp def get_transformed_template_base_class(s): base = '' while re.search(variable_template_pattern, s): base = get_transformed_template_components(s)[0] s = replace_template(s) return base def detected_class_or_struct_declaration(line): return class_regex.match(line) or struct_regex.match(line) # this method is used to eliminate some edge-cases in our pattern detections def could_be_template_specialization(line): return not friend_regex.match(line) and \ not template_regex.match(line) and \ not cpp_keywords_regex.match(line) and \ re.compile('^((?!(^\/\/)).)*$').match(line) and \ include_regex.match(line) def extract_class_documentation(content): comments = '' # used to check if the detected class is a template class template_class_definition = '' lines = content.split('\n') for line in reversed(lines): if comment_regex.match(line) : comments = line + '\n' + comments else: if template_regex.match(line) and comments == '': template_class_definition = line else: if re.compile('^((?!(^\/\/)).).*$').match(line): break; # if we have a template class then add it to the final result # in order to keep the same structure as before if(template_class_definition != ''): return comments + template_class_definition + '\n' else: return comments # this method is used to get the resulting string from the generated list # it also registers every local detected template specialization in the global list def local_list_to_string(list): final_string = '' if(len(list) > 0): for item in list: if(item not in global_transformed_templates_list): global_transformed_templates_list.append(item) final_string += item return final_string + '\n' def generate_class(name, base_name): return '\n/// @note Specialization for ' + base_name + '\n/// @addindex ' + name +'\nclass ' + name + ': public ' + base_name + '<> {};\n' def replace_template_in_line(line): while re.search(variable_template_pattern, line): line = replace_template(line) return line # this method is used to insert the generated C#-like classes above the class in which they have been first detected def insert_generated_classes(content_before, content_after, generated_class_list): classDocumentation = extract_class_documentation(content_before) content_before = content_before.replace(classDocumentation, ''); return content_before + local_list_to_string(generated_class_list) + classDocumentation + content_after; def get_right_side_comments(line): comments = '' comments_res = (right_side_comments_regex.search(line)) if comments_res: comments = comments_res.group() return comments def process_file(input_file): # used to keep a list with the C#-like generated classes local_transformed_templates_list = [] # the content variables are used to store content before and after reaching a class, a struct or a template class definition # whenever we detect a template specialization we generate the C# equivalent and if it requires a class generation # we use these two variables to add the generated classes in the right place content_before = '' content_after = '' finalContent = '' lines = input_file.read().split('\n') for line in lines: stripped_line = line.lstrip() if(detected_class_or_struct_declaration(stripped_line)): # if this is the first detection then switch the contents if(content_before == ''): content_before = content_after content_after = '' content_before = insert_generated_classes(content_before, content_after, local_transformed_templates_list) content_after = '' # clear the local list local_transformed_templates_list = [] else: if could_be_template_specialization(stripped_line): comments = get_right_side_comments(line) try: line = re.sub(comments, '', line) except: print("\nLast line read was:\n" + line + "\n") raise transformed_template_name = get_transformed_template(line) if ((not transformed_template_name in local_transformed_templates_list) and (transformed_template_name != '')): transformed_template_base_class = get_transformed_template_base_class(line) generated_class = generate_class(transformed_template_name, transformed_template_base_class) local_transformed_templates_list.append(generated_class) line = replace_template_in_line(line) + comments content_after = content_after + line + '\n' return insert_generated_classes(content_before, content_after, local_transformed_templates_list) def main(): copy_headers() for root, dirs, files in os.walk(target_path, topdown=False): for file in files: input_path = os.path.join(root, file) file_handle, absolute_path = mkstemp() with open(input_path, 'r') as input_file, os.fdopen(file_handle, 'w') as output_file: output_file.write(process_file(input_file)) remove_readonly(input_path) shutil.move(absolute_path, input_path) if __name__ == '__main__': main()