From 9c2a5ee7e0fb54a7d4fda7acc660038245520641 Mon Sep 17 00:00:00 2001 From: mike-reilly-bg Date: Wed, 5 Jul 2023 02:29:48 -0400 Subject: [PATCH 1/2] added fix stubs script --- release/stubs/fix_stubs_script.py | 218 ++++++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 release/stubs/fix_stubs_script.py diff --git a/release/stubs/fix_stubs_script.py b/release/stubs/fix_stubs_script.py new file mode 100644 index 00000000..89af8af5 --- /dev/null +++ b/release/stubs/fix_stubs_script.py @@ -0,0 +1,218 @@ +""" +This is a script that fixes shortcommings in the existing iron-stubs output, as of 7/5/2023. + +Right now, iron-stubs gets types right in the comments it adds, but not the code. It also produces +stubs that have a lot of type errors, and no import statements. + +To fix this, this script will re-write the functions and classes with correct syntax. + +It will also go through every type in every function, property, and class, and look +for it in all the other stubs files in the same root. If it finds the class in another stub file, +it will add an import statement at the top of the file. Note that it will use the first class +definition it finds, so if names are not unique and you want to limit the stubs folders it looks through, +remove the unwanted stubs folders. In my case, all the external references were from System + +If it does not find a class's inherited class in the current file, another stubs file, or +the builtin types, it will omit that inherited class from the revised code. + +This code is not super well written (sorry) (...or commented), but as of now it creates stub files that are +100% warning- and error-free. + +Compared to the current repo, I rebuilt my System stubs like this to get the latest datatypes: +C:\'Program Files'\'IronPython 2.7'\ipy.exe -m ironstubs make mscorlib --path="C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319" + +How to use this script: +Put this script in the root of the stubs directory. +You will be prompted for the name of the Stubs folder that you want to clean up. +Path syntax is currently handled for Windows (i.e. \), but can be easily fixed for linux + +written by Mike Reilly +mreilly92@gmail.com +""" + + +import os +import re +from pathlib import Path + +# Prompt for directory +directory = input("Enter the directory path: ") +# directory = 'test' + +# Make a backup +backup_directory = '_' + directory +# shutil.copytree(directory, backup_directory) # commented out as per request +# print(f"Backup made at {backup_directory}") + +# Update the files + +class_import_dictionary = {} + +def format_new_code(_string): + return _string.replace("'value'", 'T') + +def find_import_declaration(_class_name): + if _class_name in class_import_dictionary: + return class_import_dictionary[_class_name] + + # Regular expression to match the class declaration. + _class_pattern = re.compile(rf"class\s+{_class_name}(?=[\(:\s])") + + for _root, _dirs, _files in os.walk('.'): + for _filename in _files: + # Only consider Python files. + if _filename.endswith('.py'): + _file_path = os.path.join(_root, _filename) + # print(f"checking for class {_class_name} in file {_filename}") + with open(_file_path, 'r') as _file: + for _line in _file: + # Check the class pattern. + if _class_pattern.search(_line): + # If a match is found, create the import declaration. + # Remove the './' from the root, and replace '/' with '.' to follow Python's import syntax. + if _filename == '__init__.py': + _module_path = _file_path.split('\\__init__.py')[0].replace('\\','.').lstrip('..') + else: + _module_path = _file_path.split('.py')[0].replace('\\','.').lstrip('..') + _import_declaration = f"from {_module_path} import {_class_name}\n" + class_import_dictionary[_class_name] = _import_declaration + return _import_declaration + return None + +def handle_class_names(_name): + if _name is None or _name == '' or _name == 'value': + return + _name = _name.strip("'").strip().strip('(').strip(')').strip("'").strip() + if 'tuple' in _name: + if len(_name.split("(of ")) > 1: + _name = _name.split("(of ") + _name1 = handle_class_names(_name[1]) + elif ',' in _name: + _name = _name.split(',') + _name1 = handle_class_names(_name[0]) + if len(_name) > 1: + _name2 = handle_class_names(_name[1]) + _name = "(" + _name1 + ', ' + _name2 + ')' + elif '[' in _name: + _name = _name.split('[') + _name1 = handle_class_names(_name[0]) + if len(_name) > 1: + _name2 = handle_class_names(_name[1].strip().strip(']')) + _name = _name1 + '["' + _name2 + '"]' + elif not _name in class_names_in_file and not _name in __builtins__: + # print(f"looking for new class {_name}") + class_names_in_file.append(_name) + _import_declaration = find_import_declaration(_name) + if _import_declaration is not None: + import_lines.append(_import_declaration) + print(f"{_name} found. import syntax: {_import_declaration.strip()}") + return _name + +for path in Path(directory).rglob('*.py'): + print(f"Working on {path}") + with open(path, 'r+') as file: + lines = file.readlines() + + # Collect all class names in the file + class_names_in_file = [line.split(' ')[1].split(':')[0].split('(')[0] for line in lines if line.lstrip().startswith('class')] + + import_lines = [] + new_lines = [] # a new list to store the updated lines + new_lines.append('from typing import TypeVar\n') + new_lines.append("\nT = TypeVar('T')\n\n") + + i = 0 + while i < len(lines): + new_lines.append(lines[i]) + + prev_line_index = i - 1 # get the previous line index + prev_line_indent = len(lines[prev_line_index]) - len(lines[prev_line_index].lstrip()) # get the indentation level of previous line + + if lines[i].startswith('# Error'): + new_lines.pop() + new_lines.append(f"{prev_line_indent*' '}{lines[i].strip()}\n") + new_lines.append(f"{prev_line_indent*' '} pass") + + elif '"""' in lines[i]: + new_lines.pop() + comment_block = lines[i] + + if not comment_block.strip().endswith('"""') or comment_block.count('"""') % 2 != 0: # check if it's a single-line block comment + while True: + i += 1 + comment_block += lines[i] + if '"""' in lines[i] and lines[i].count('"""') % 2 != 0: # check if it's the end of a block comment + break + + if '->' in comment_block: + + new_lines.pop() + comment_lines = comment_block.strip().split("\n") + getter_type = setter_type = None + for line in comment_lines: + line = line.strip().lstrip('"""').strip() # remove """ at the start of lines + if line.startswith('Get:'): + getter_type = line.split('->')[1].strip() + elif line.startswith('Set:'): + setter_type = line.split('=')[1].strip() + + if 'property' in lines[prev_line_index]: + property_name = lines[prev_line_index].split('=')[0].strip() + if not getter_type: + getter_type = setter_type + if not setter_type: + setter_type = getter_type + getter_type = handle_class_names(getter_type) + setter_type = handle_class_names(setter_type) + new_code = f"{prev_line_indent*' '}@property\n{prev_line_indent*' '}def {property_name}(self) -> '{getter_type}':\n{prev_line_indent*' '} return self._{property_name}\n{prev_line_indent*' '}@{property_name}.setter\n{prev_line_indent*' '}def {property_name}(self, value: '{setter_type}') -> None:\n{prev_line_indent*' '} self._{property_name} = value" + + elif 'def' in lines[prev_line_index]: + method_line = lines[prev_line_index].strip() + method_name = method_line.split(' ')[1].split('(')[0] + for line in [line for line in comment_lines if '->' in line]: + return_type = line.split('->')[1].strip().split("\n")[0].strip('"""') + return_type = handle_class_names(return_type) + new_code = f"{prev_line_indent*' '}def {method_name}(self) -> '{return_type}':\n{prev_line_indent*' '}" + + new_lines.append(format_new_code(new_code+'\n')) + old_commented_out_code = f"{prev_line_indent*' '}# {lines[prev_line_index].lstrip()}" + new_lines.append(old_commented_out_code) # Commenting out existing code + + comment_block = comment_block.replace('\n\n\n\n','\n') + if comment_block.strip().split('\n')[-1] == '"""': + comment_block = comment_block.rstrip().split('\n') + comment_block.pop() + comment_block.append(prev_line_indent*' ' + '"""\n') + comment_block = ''.join(comment_block) + new_lines.append(comment_block) + + elif 'class ' in lines[i] and '(' in lines[i] and not lines[i].strip().startswith('#'): # Check if the line is a class definition + class_line_index = i + class_line_indent = len(lines[class_line_index]) - len(lines[class_line_index].lstrip()) # get the indentation level of class line + class_name, superclass_names = lines[class_line_index].split(' ')[1].split('(')[0], lines[class_line_index].split(' ')[1].split('(')[1].rstrip('):\n').split(',') + + # Keep only superclasses that are defined in the same file + for name in superclass_names: + name = handle_class_names(name) + superclass_names = [name for name in superclass_names if name in class_names_in_file] + + if superclass_names: + new_class_code = f"{class_line_indent*' '}class {class_name}({', '.join(superclass_names)}):" + else: + new_class_code = f"{class_line_indent*' '}class {class_name}:" + + new_lines.pop() + new_lines.append(format_new_code(new_class_code+'\n')) + new_lines.append(f"{class_line_indent*' '}# {lines[class_line_index]}") # Commenting out existing code + + elif lines[i].strip() == "None = None": + new_lines.pop() + + i += 1 + for line in import_lines: + new_lines.insert(0, line) + file.seek(0) + file.truncate() + file.write(''.join(new_lines)) + +print("\n\nStub files updated!") \ No newline at end of file From 77e1264882c34b9248ee720acdaf9be3ba506639 Mon Sep 17 00:00:00 2001 From: mike-reilly-bg Date: Thu, 6 Jul 2023 00:10:51 -0400 Subject: [PATCH 2/2] Fixed a lot of functionality with class args and overloaded defs. Still some issues where it tries to make functions from commented lines, should be minor --- release/stubs/fix_stubs_script.py | 319 +++++++++++++++++++++++------- 1 file changed, 243 insertions(+), 76 deletions(-) diff --git a/release/stubs/fix_stubs_script.py b/release/stubs/fix_stubs_script.py index 89af8af5..d02a58f1 100644 --- a/release/stubs/fix_stubs_script.py +++ b/release/stubs/fix_stubs_script.py @@ -15,8 +15,8 @@ If it does not find a class's inherited class in the current file, another stubs file, or the builtin types, it will omit that inherited class from the revised code. -This code is not super well written (sorry) (...or commented), but as of now it creates stub files that are -100% warning- and error-free. +This code is not super well written (sorry), but as of now it creates stub files that make full use +of type hinting. Compared to the current repo, I rebuilt my System stubs like this to get the latest datatypes: C:\'Program Files'\'IronPython 2.7'\ipy.exe -m ironstubs make mscorlib --path="C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319" @@ -30,34 +30,56 @@ mreilly92@gmail.com """ - +from fileinput import filename import os import re from pathlib import Path +from tkinter import N -# Prompt for directory +# Prompt for assembly stub directory directory = input("Enter the directory path: ") -# directory = 'test' - -# Make a backup -backup_directory = '_' + directory -# shutil.copytree(directory, backup_directory) # commented out as per request -# print(f"Backup made at {backup_directory}") -# Update the files +# initialize empty dictionary for import statements +resolved_class_dictionary = {} +unresolved_class_list = [] + +def split_ignore_brackets(s): + stack = [] + result = [] + current = '' + for char in s: + if char in '([': + stack.append(char) + elif char in ')]' and stack: + stack.pop() + + if char == ',' and not stack: + result.append(current) + current = '' + else: + current += char -class_import_dictionary = {} + if current: + result.append(current) -def format_new_code(_string): - return _string.replace("'value'", 'T') + return result def find_import_declaration(_class_name): - if _class_name in class_import_dictionary: - return class_import_dictionary[_class_name] + if _class_name in resolved_class_dictionary: + # import statement for this class name was already resolved during this run + return resolved_class_dictionary[_class_name] + + if _class_name in unresolved_class_list: + return None + print(f"{file_name}-> Looking for class {_class_name}.") # Regular expression to match the class declaration. - _class_pattern = re.compile(rf"class\s+{_class_name}(?=[\(:\s])") + try: + _class_pattern = re.compile(rf"class\s+{_class_name}(?=[\(:\s])") + except re.error: + return None + # walk all files in directory for _root, _dirs, _files in os.walk('.'): for _filename in _files: # Only consider Python files. @@ -74,145 +96,290 @@ def find_import_declaration(_class_name): _module_path = _file_path.split('\\__init__.py')[0].replace('\\','.').lstrip('..') else: _module_path = _file_path.split('.py')[0].replace('\\','.').lstrip('..') + # write the import declaration _import_declaration = f"from {_module_path} import {_class_name}\n" - class_import_dictionary[_class_name] = _import_declaration + # store the import declaration for this class name + resolved_class_dictionary[_class_name] = _import_declaration + # return the import declaration return _import_declaration + # class name not found + unresolved_class_list.append(_class_name) return None -def handle_class_names(_name): - if _name is None or _name == '' or _name == 'value': - return - _name = _name.strip("'").strip().strip('(').strip(')').strip("'").strip() - if 'tuple' in _name: - if len(_name.split("(of ")) > 1: - _name = _name.split("(of ") - _name1 = handle_class_names(_name[1]) - elif ',' in _name: - _name = _name.split(',') - _name1 = handle_class_names(_name[0]) +def handle_class_names(_name, return_with_double_quotes = True): + print(f"{file_name}-> Handling class {_name}") + # remove garbage from class name + if _name is None or _name == '': + return "" + _name = _name.strip("'").strip().strip("'").strip() + if _name in __builtins__: + return _name + elif _name in valid_class_names_for_file: + if return_with_double_quotes: + return f"\"{_name}\"" + else: + return _name + elif 'tuple (of' in _name: + # handle tuple(of foo) syntax + if len(_name.split("(of ",1)) > 1: + _name = _name.split("(of ",1) + _name = handle_class_names(_name[1], return_with_double_quotes) + _name = f"tuple (of {_name})" + return _name + elif _name.endswith(")") and _name.startswith("("): + # handle (foo, bar) syntax + _name = _name.strip('(').strip(')') + _name = "(" + handle_class_names(_name, return_with_double_quotes) + ")" + return _name + elif len(split_ignore_brackets(_name)) > 1: + # handle (foo, bar) syntax + _name = _name.strip('(').strip(')') + _name = split_ignore_brackets(_name) + _name_str = "" + for _nam in _name: + test = handle_class_names(_nam, return_with_double_quotes) + _name_str = _name_str + handle_class_names(_nam, return_with_double_quotes) + ", " + _name_str = _name_str.strip(", ") + return _name_str + elif _name.endswith("]") and "[" in _name: + # handle foo[bar] syntax + _name = _name[:-1] + _name = _name.split('[', 1) if len(_name) > 1: - _name2 = handle_class_names(_name[1]) - _name = "(" + _name1 + ', ' + _name2 + ')' - elif '[' in _name: - _name = _name.split('[') - _name1 = handle_class_names(_name[0]) - if len(_name) > 1: - _name2 = handle_class_names(_name[1].strip().strip(']')) - _name = _name1 + '["' + _name2 + '"]' - elif not _name in class_names_in_file and not _name in __builtins__: - # print(f"looking for new class {_name}") - class_names_in_file.append(_name) + _name_str = handle_class_names(_name[0], return_with_double_quotes) + "[" + handle_class_names(_name[1], return_with_double_quotes) + "]" + else: + _name_str = "[" + handle_class_names(_name[1], return_with_double_quotes) + "]" + return _name_str + elif 'value' == _name: + # replace all instances of 'value' with 'T' (refer to hardcoded import statement T = TypeVar('T')) + return 'T' + elif not _name in valid_class_names_for_file: + # class not found in this file, and is not builtin + # add to known classes for this file + valid_class_names_for_file.append(_name) + # get import declaration _import_declaration = find_import_declaration(_name) if _import_declaration is not None: + # import declaration found import_lines.append(_import_declaration) - print(f"{_name} found. import syntax: {_import_declaration.strip()}") - return _name + print(f"{file_name}-> {_name} found. import syntax: {_import_declaration.strip()}") + if return_with_double_quotes: + return f"\"{_name}\"" + else: + return _name + else: + if return_with_double_quotes: + return f"\"{_name}\"" + else: + return _name + # return name in double quotes to avoid editor class type warnings for path in Path(directory).rglob('*.py'): + file_name = path print(f"Working on {path}") with open(path, 'r+') as file: + # read file lines lines = file.readlines() # Collect all class names in the file - class_names_in_file = [line.split(' ')[1].split(':')[0].split('(')[0] for line in lines if line.lstrip().startswith('class')] + valid_class_names_for_file = [line.split(' ')[1].split(':')[0].split('(')[0] for line in lines if line.lstrip().startswith('class')] + # a list of import statements to insert at the top of the file import_lines = [] - new_lines = [] # a new list to store the updated lines + # write buffer as a list of new lines + new_lines = [] + + # import lines used in this code + new_lines.append('from typing import overload\n') new_lines.append('from typing import TypeVar\n') new_lines.append("\nT = TypeVar('T')\n\n") i = 0 while i < len(lines): + # store the line in the write buffer new_lines.append(lines[i]) - prev_line_index = i - 1 # get the previous line index - prev_line_indent = len(lines[prev_line_index]) - len(lines[prev_line_index].lstrip()) # get the indentation level of previous line + # get the previous line index and indent + prev_line_index = i - 1 + prev_line_indent = len(lines[prev_line_index]) - len(lines[prev_line_index].lstrip()) + # special case, replace these lines with 'pass' if lines[i].startswith('# Error'): + # remove the error comment line from the write buffer, which is not indented properly new_lines.pop() + # reinsert the error comment line with correct indenting new_lines.append(f"{prev_line_indent*' '}{lines[i].strip()}\n") + # insert pass with correct indenting new_lines.append(f"{prev_line_indent*' '} pass") + # check if line is the start of a block comment elif '"""' in lines[i]: + # remove the start of the block comment from the write buffer, we will handle it later as one chunk new_lines.pop() + # start storing the commend block in a big string comment_block = lines[i] - if not comment_block.strip().endswith('"""') or comment_block.count('"""') % 2 != 0: # check if it's a single-line block comment + # check if it's a single-line block comment + if not comment_block.strip().endswith('"""') or comment_block.count('"""') % 2 != 0: + # not single-line block comment. + # loop until we find the end of the comment block while True: + # increment file line number i += 1 + # add current line to comment block string comment_block += lines[i] - if '"""' in lines[i] and lines[i].count('"""') % 2 != 0: # check if it's the end of a block comment + # check if it's the end of the block comment + if '"""' in lines[i] and lines[i].count('"""') % 2 != 0: + # exit while loop, we have gotten the whole comment block break - + + # check if there is a type hint in the comment block if '->' in comment_block: - + #type hint found + # remove the line of code before the comment block from the write buffer new_lines.pop() + # get list of lines from the comment block comment_lines = comment_block.strip().split("\n") - getter_type = setter_type = None - for line in comment_lines: - line = line.strip().lstrip('"""').strip() # remove """ at the start of lines - if line.startswith('Get:'): - getter_type = line.split('->')[1].strip() - elif line.startswith('Set:'): - setter_type = line.split('=')[1].strip() + # handling class property definitions (examine line before comment block) if 'property' in lines[prev_line_index]: + # initialize types + getter_type = setter_type = None + # search every line in the comment block + for line in comment_lines : + # remove white space and """ at the start of each line + line = line.strip().lstrip('"""').strip() + if line.startswith('Get:'): + # Get the type hint from a line that starts with "Get:", remove white space + getter_type = line.split('->')[1].strip() + elif line.startswith('Set:'): + # Get the type hint from a line that starts with "Set:", remove white space + setter_type = line.split('=')[1].strip() + # extract the property name property_name = lines[prev_line_index].split('=')[0].strip() if not getter_type: + # Catch if get type hint not specified, use set type hint getter_type = setter_type if not setter_type: + # Catch if set type hint not specified, use get type hint setter_type = getter_type + # look for type hint class, generate import statement if needed, reformat class name getter_type = handle_class_names(getter_type) + # look for type hint class, generate import statement if needed, reformat class name setter_type = handle_class_names(setter_type) - new_code = f"{prev_line_indent*' '}@property\n{prev_line_indent*' '}def {property_name}(self) -> '{getter_type}':\n{prev_line_indent*' '} return self._{property_name}\n{prev_line_indent*' '}@{property_name}.setter\n{prev_line_indent*' '}def {property_name}(self, value: '{setter_type}') -> None:\n{prev_line_indent*' '} self._{property_name} = value" + # new property declaration using type hints from comment block: + new_code = f"{prev_line_indent*' '}@property\n{prev_line_indent*' '}def {property_name}(self) -> {getter_type}:\n{prev_line_indent*' '} return self._{property_name}\n{prev_line_indent*' '}@{property_name}.setter\n{prev_line_indent*' '}def {property_name}(self, value: '{setter_type}') -> None:\n{prev_line_indent*' '} self._{property_name} = value" + # handling class method definitions (examine line before comment block) elif 'def' in lines[prev_line_index]: + # get the line of code before the comment block method_line = lines[prev_line_index].strip() - method_name = method_line.split(' ')[1].split('(')[0] - for line in [line for line in comment_lines if '->' in line]: + # extract the method name + method_name = method_line.strip().split('def ')[1].split('(',1)[0] + # initialize new code string so it can be added to + new_code = "" + # get lines from comment block containing type hints + comment_lines_with_def_type_hinting = [line for line in comment_lines if '->' in line] + if len(comment_lines_with_def_type_hinting) > 1: + # multiple type hints, is an overloaded function, set up overloaded syntax + overload_decorator = f"{prev_line_indent*' '}@overload\n" + overload_continuation = f"{prev_line_indent*' '} ...\n" + else: + # not an overloaded function + overload_decorator = "" + overload_continuation = "" + # loop through all lines with type hinting + for line in comment_lines_with_def_type_hinting: + # get the type hint return_type = line.split('->')[1].strip().split("\n")[0].strip('"""') + # look for type hint class, generate import statement if needed, reformat class name return_type = handle_class_names(return_type) - new_code = f"{prev_line_indent*' '}def {method_name}(self) -> '{return_type}':\n{prev_line_indent*' '}" + # get the function definition + func_def = line.split('->')[0].strip().strip('"""').strip() + # get the individual function parameters + arg_list = split_ignore_brackets(func_def.split('(',1)[1].strip(')')) + # initialize argument string so it can be added to + args_string = "" + # loop over all arguments (parameters) + for arg in arg_list: + if len(arg.split(":")) > 1: + # extract the parameter name + param_name = arg.split(":")[0].strip() + # extract the parameter type + param_type = arg.split(":")[1].strip() + # look for type hint class, generate import statement if needed, reformat class name + param_type = handle_class_names(param_type) + # reconstruct sinle parameter + param = f"{param_name}: {param_type}" + else: + # no type specified, just use parameter string as-is + param = arg + # add to args string, add comma + args_string = args_string + param + "," + # remove the last comma + args_string = args_string.strip(",") + # construct new method code + new_code = new_code + f"{overload_decorator}{prev_line_indent*' '}def {method_name}({args_string}) -> {return_type}:\n{overload_continuation}" - new_lines.append(format_new_code(new_code+'\n')) + # write new code to write buffer + new_lines.append(new_code+'\n') + # comment out old code old_commented_out_code = f"{prev_line_indent*' '}# {lines[prev_line_index].lstrip()}" - new_lines.append(old_commented_out_code) # Commenting out existing code + # write old commented out code to buffer + new_lines.append(old_commented_out_code) + # reduce white space in comment blocks comment_block = comment_block.replace('\n\n\n\n','\n') + # fix the indenting of the last end of the comment block if comment_block.strip().split('\n')[-1] == '"""': comment_block = comment_block.rstrip().split('\n') comment_block.pop() comment_block.append(prev_line_indent*' ' + '"""\n') comment_block = ''.join(comment_block) + # write the original comment block to the write buffer new_lines.append(comment_block) - elif 'class ' in lines[i] and '(' in lines[i] and not lines[i].strip().startswith('#'): # Check if the line is a class definition + # check if line is a class declaration + elif 'class ' in lines[i] and '(' in lines[i] and not lines[i].strip().startswith('#'): + test_line = lines[i] class_line_index = i - class_line_indent = len(lines[class_line_index]) - len(lines[class_line_index].lstrip()) # get the indentation level of class line - class_name, superclass_names = lines[class_line_index].split(' ')[1].split('(')[0], lines[class_line_index].split(' ')[1].split('(')[1].rstrip('):\n').split(',') - - # Keep only superclasses that are defined in the same file - for name in superclass_names: - name = handle_class_names(name) - superclass_names = [name for name in superclass_names if name in class_names_in_file] - - if superclass_names: - new_class_code = f"{class_line_indent*' '}class {class_name}({', '.join(superclass_names)}):" + # get the indentation level of class line + class_line_indent = len(lines[class_line_index]) - len(lines[class_line_index].lstrip()) + # get list of inherited classes (superclasses) + class_name = lines[class_line_index].strip().split('class ')[1].split('(',1)[0] + test = lines[class_line_index].strip().split('class ')[1].split('(',1)[1].strip("#").strip().strip(":").strip(')') + args = handle_class_names(lines[class_line_index].strip().split('class ')[1].split('(',1)[1].strip("#").strip().strip(":").strip(')'), False) + + # create new class definition code + if args: + new_class_code = f"{class_line_indent*' '}class {class_name}({args}):" else: new_class_code = f"{class_line_indent*' '}class {class_name}:" + # removing existing class declaration new_lines.pop() - new_lines.append(format_new_code(new_class_code+'\n')) - new_lines.append(f"{class_line_indent*' '}# {lines[class_line_index]}") # Commenting out existing code + # add new class declaration + new_lines.append(new_class_code+'\n') + # Commenting out and add existing class declaration + new_lines.append(f"{class_line_indent*' '}# {lines[class_line_index]}") + # special case, remove garbage code elif lines[i].strip() == "None = None": new_lines.pop() + # next line i += 1 + + # insert import statements into write buffer for line in import_lines: new_lines.insert(0, line) + + # write buffer to file file.seek(0) file.truncate() file.write(''.join(new_lines)) + # snext file + +# all files finishes print("\n\nStub files updated!") \ No newline at end of file