def _process_star(cls, red, stars, skip_lineno=False): """ _process_star is designed to replace from X import * methods. :param red: redbaron process. Unused in this method. :type red: redbardon.RedBaron :param stars: List of redbaron nodes that matched for this proc. :type stars: list """ mappings = {} for star in stars: from_import = star.parent binding = from_import.value[0] second_level_modules = None if len(star.parent.value) > 1: second_level_modules = [star.parent.value[1].dumps()] if len(star.parent.value) > 2: pass children = cls._get_children(binding.dumps(), second_level_modules) if second_level_modules is None: second_level_modules = children text = "from {binding} import {slm}".format( binding="Qt", slm=", ".join([name for name in second_level_modules])) change(logger=EXPAND_STARS_LOG, node=star.parent, replacement=text, skip_lineno=skip_lineno) mappings.update(children) # star.replace( # text # ) return mappings
def _process_to_methods(red, objects, skip_lineno=False, **kwargs): """ Attempts at fixing the "toString" "toBool" "toPyObject" etc PyQt4-apiv1.0 helper methods. :param red: redbaron process. Unused in this method. :type red: redbardon.RedBaron :param objects: List of redbaron nodes that matched for this proc. :type objects: list :param skip_lineno: Global "skip_lineno" flag. :type skip_lineno: bool """ for node in objects: raw = node.parent.dumps() changed = _conversion_methods.to_methods(raw) if changed != raw: change( logger=PSEP_LOG, node=node.parent, replacement=changed, skip_lineno=skip_lineno, ) node.parent.replace(changed) continue
def _convert_root_name_imports(red, aliases, skip_lineno=False): """ _convert_root_name_imports is a function that should be used in cases where the original code just imported the python binding and did not import any second level modules. For example: ``` import PySide ``` :param red: The redbaron ast. :type red: redbaron.RedBaron :param aliases: Aliases is the replacement information that is build automatically from qt_py_convert. :type aliases: dict :param skip_lineno: An optional performance flag. By default, when the script replaces something, it will tell you which line it is replacing on. This can be useful for tracking the places that changes occurred. When you turn this flag on however, it will not show the line numbers. This can give great performance increases because redbaron has trouble calculating the line number sometimes. :type skip_lineno: bool """ def filter_function(value): """A filter delegate for our red.find_all function.""" return value.dumps().startswith("Qt.") matches = red.find_all("AtomTrailersNode", value=filter_function) matches += red.find_all("DottedNameNode", value=filter_function) lstrip_qt_regex = re.compile(r"^Qt\.", ) if matches: MAIN_LOG.debug( color_text( text="====================================", color=ANSI.colors.purple, )) MAIN_LOG.debug( color_text( text="Replacing top level binding imports.", color=ANSI.colors.purple, style=ANSI.styles.underline, )) for node in matches: name = lstrip_qt_regex.sub("", node.dumps(), count=1) root_name = name.split(".")[0] if root_name in COMMON_MODULES: aliases["root_aliases"].add(root_name) change(logger=MAIN_LOG, node=node, replacement=name, skip_lineno=skip_lineno) node.replace(name) else: MAIN_LOG.warning( "Unknown second level module from the Qt package \"{}\"". format(color_text(text=root_name, color=ANSI.colors.orange)))
def _process_qstringlist(red, objects, skip_lineno=False, **kwargs): """ _process_qstringlist is designed to replace QStringList code. :param red: redbaron process. Unused in this method. :type red: redbardon.RedBaron :param objects: List of redbaron nodes that matched for this proc. :type objects: list :param skip_lineno: Global "skip_lineno" flag. :type skip_lineno: bool """ # TODO: Find different usage cases of QStringList. # Probably just need support for construction and isinstance. # Replace each node for node in objects: raw = node.parent.dumps() changed = re.sub(r"((?:QtCore\.)?QStringList)", "list", raw) if changed != raw: change( logger=PSEP_LOG, node=node.parent, replacement=changed, skip_lineno=skip_lineno, ) node.parent.replace(changed)
def _process_qstring(red, objects, skip_lineno=False, **kwargs): """ _process_qstring is designed to replace QString code. :param red: redbaron process. Unused in this method. :type red: redbardon.RedBaron :param objects: List of redbaron nodes that matched for this proc. :type objects: list :param skip_lineno: Global "skip_lineno" flag. :type skip_lineno: bool """ # Replace each node for node in objects: raw = node.parent.dumps() changed = re.sub(r"((?:QtCore\.)?QString(?:\.fromUtf8)?)", text_type.__name__, raw) if changed != raw: change( logger=PSEP_LOG, node=node.parent, replacement=changed, skip_lineno=skip_lineno, ) node.parent.replace(changed)
def _no_second_level_module(node, _child_parts, skip_lineno=False): replacement = "import Qt" change( logger=IMPORTS_LOG, node=node, replacement=replacement, skip_lineno=skip_lineno ) node.replace(replacement)
def _process_qvariant(red, objects, skip_lineno=False, **kwargs): """ _process_qvariant is designed to replace QVariant code. :param red: redbaron process. Unused in this method. :type red: redbardon.RedBaron :param objects: List of redbaron nodes that matched for this proc. :type objects: list :param skip_lineno: Global "skip_lineno" flag. :type skip_lineno: bool """ qvariant_expr = re.compile( r"(?:QtCore\.)?QVariant(?P<is_instance>\((?P<value>.*)\))?") # Replace each node for node in objects: raw = node.parent.dumps() matched = qvariant_expr.search(raw) if matched: if not matched.groupdict()["is_instance"]: # We have the usage of a QVariant Class object. # This leads to an invalid statement and cannot be # resolved in api 2.0. # We are adding it to warnings and continuing on. ErrorClass.from_node(node=node, reason=""" As of api v2.0, there is no concept of a "QVariant" object. Usage of the class object directly cannot be translated into something that \ can consistantly be relied on. You will probably want to remove the usage of this entirely.""") continue else: # If it was used as an instance (most cases). def replacement(match): """regex sub function""" # Some edge case logic here. # Was having issues replacing the following code: # return QtCore.QVariant() # There was no parameter...So now that becomes: # return None if not match.groupdict()["value"]: return "None" return match.groupdict()["value"] # We have an instance of a QVariant used. changed = qvariant_expr.sub(replacement, raw) if changed != raw: change( logger=PSEP_LOG, node=node.parent, replacement=changed.strip(" "), skip_lineno=skip_lineno, ) node.parent.replace(changed.strip(" "))
def _process_import(cls, red, objects, skip_lineno=False): """ _process_import is designed to replace from import methods. :param red: redbaron process. Unused in this method. :type red: redbardon.RedBaron :param objects: List of redbaron nodes that matched for this proc. :type objects: list :param skip_lineno: Global "skip_lineno" flag. :type skip_lineno: bool """ binding_aliases = ALIAS_DICT mappings = {} # Replace each node for node, binding in objects: from_import_parts = cls._get_import_parts(node, binding) if len(from_import_parts) and from_import_parts[0]: second_level_module = from_import_parts[0] else: cls._no_second_level_module(node.parent, from_import_parts, skip_lineno=skip_lineno) binding_aliases["bindings"].add(binding) for target in node.parent.targets: binding_aliases["root_aliases"].add(target.value) continue for _from_as_name in node.parent.targets: if _from_as_name.type in IGNORED_IMPORT_TARGETS: continue if _from_as_name.type == "star": # TODO: Make this a flag and make use the expand module. _, star_mappings = stars_process(red) mappings.update(star_mappings) else: key = _from_as_name.target or _from_as_name.value value = ".".join( from_import_parts) + "." + _from_as_name.value mappings[key] = value replacement = "from Qt import {key}".format( key=second_level_module) change(logger=FROM_IMPORTS_LOG, node=node.parent, replacement=replacement, skip_lineno=skip_lineno) node.parent.replace(replacement) binding_aliases["bindings"].add(binding) for target in node.parent.targets: binding_aliases["root_aliases"].add(target.value) if binding not in binding_aliases: binding_aliases[binding] = set() binding_aliases[binding] = binding_aliases[binding].union( set([target.value for target in node.parent.targets])) return binding_aliases, mappings
def _no_second_level_module(node, _parts, skip_lineno=False): text = "from Qt import {key}".format( key=", ".join([target.value for target in node.targets])) change(logger=FROM_IMPORTS_LOG, node=node, replacement=text, skip_lineno=skip_lineno) node.replace(text)
def _process_qsignal(red, objects, skip_lineno=False, explicit_signals_flag=False): """ _process_qsignal is designed to replace QSignal code. It calls out to the _qsignal module and can fix disconnects, connects, and emits. :param red: redbaron process. Unused in this method. :type red: redbardon.RedBaron :param objects: List of redbaron nodes that matched for this proc. :type objects: list :param skip_lineno: Global "skip_lineno" flag. :type skip_lineno: bool """ for node in objects: raw = node.parent.dumps() if "disconnect" in raw: changed = _qsignal.process_disconnect( raw, explicit=explicit_signals_flag) if changed != raw: change( logger=PSEP_LOG, node=node.parent, replacement=changed, skip_lineno=skip_lineno, ) node.parent.replace(changed) continue if "connect" in raw: changed = _qsignal.process_connect( raw, explicit=explicit_signals_flag) if changed != raw: change( logger=PSEP_LOG, node=node.parent, replacement=changed, skip_lineno=skip_lineno, ) node.parent.replace(changed) continue if "emit" in raw: changed = _qsignal.process_emit(raw, explicit=explicit_signals_flag) if changed != raw: change( logger=PSEP_LOG, node=node.parent, replacement=changed, skip_lineno=skip_lineno, ) node.parent.replace(changed) continue
def _process_import(cls, red, objects, skip_lineno=False): """ _process_import is designed to replace import methods. :param red: redbaron process. Unused in this method. :type red: redbardon.RedBaron :param objects: List of redbaron nodes that matched for this proc. :type objects: list :param skip_lineno: Global "skip_lineno" flag. :type skip_lineno: bool """ binding_aliases = ALIAS_DICT mappings = {} # Replace each node for node, binding in objects: for child_index, child in enumerate(node): _child_name = cls._build_child_name(child) _child_as_name = child.target if _child_name.split(".")[0] not in __supported_bindings__ \ and _child_as_name not in __supported_bindings__: # Only one of our multi import node's children is relevant. continue _child_parts = _child_name.replace(binding, "") _child_parts = _child_parts.lstrip(".").split(".") # Check to see if there is a second level module if len(_child_parts) and _child_parts[0]: second_level_module = _child_parts[0] else: if len(node) == 1: # Only one in the import: "import PySide" cls._no_second_level_module(node.parent, _child_parts) else: # Multiple in the import: "import PySide, os" node_parent = node.parent node.pop(child_index) repl = node.parent.dumps() + "\nimport Qt" change( logger=IMPORTS_LOG, node=node_parent, replacement=repl, skip_lineno=skip_lineno ) node.parent.replace(repl) if _child_as_name: mappings[_child_as_name] = "Qt" else: mappings[_child_name] = "Qt" binding_aliases["bindings"].add(binding) continue mappings[_child_as_name or _child_name] = ".".join( _child_parts ) change( logger=IMPORTS_LOG, node=node.parent, replacement="from Qt import {key}".format( key=second_level_module ), skip_lineno=skip_lineno ) node.parent.replace( "from Qt import {key}".format(key=second_level_module) ) binding_aliases["bindings"].add(binding) binding_aliases["root_aliases"].add(second_level_module) if binding not in binding_aliases: binding_aliases[binding] = set() binding_aliases[binding].add(second_level_module) return binding_aliases, mappings
def _cleanup_imports(red, aliases, mappings, skip_lineno=False): """ _cleanup_imports fixes the imports. Initially changing them as per the following: >>> from PyQt4 import QtGui, QtCore to >>> from Qt import QtGui, QtCore for each binding. It doesn't have enough knowledge of your script at this point to know if you need QtWidgets or if the ones you import are all used. This will get reflected at the end. :param red: The redbaron ast. :type red: redbaron.RedBaron :param aliases: Aliases is the replacement information that is build automatically from qt_py_convert. :type aliases: dict :param mappings: Mappings is information about the bindings that are used. :type mappings: dict :param skip_lineno: An optional performance flag. By default, when the script replaces something, it will tell you which line it is replacing on. This can be useful for tracking the places that changes occurred. When you turn this flag on however, it will not show the line numbers. This can give great performance increases because redbaron has trouble calculating the line number sometimes. :type skip_lineno: bool """ replaced = False deletion_index = [] imps = red.find_all("FromImportNode") imps += red.find_all("ImportNode") MAIN_LOG.debug( color_text(text="===========================", color=ANSI.colors.blue)) MAIN_LOG.debug( color_text( text="Consolidating Import lines.", color=ANSI.colors.blue, style=ANSI.styles.underline, )) for child in imps: for value in child.value: value_str = value.value try: value_str = value_str.dumps() except AttributeError: pass if value.value == "Qt" or value_str in __suplimentary_bindings__: if not replaced: names = filter( lambda a: True if a in COMMON_MODULES else False, aliases["used"], ) if not names: # Attempt to build names from input aliases. members = filter( lambda a: True if a in mappings else False, aliases["root_aliases"], ) names = [] for member in members: names.append(mappings[member].split(".")[0]) if not names: MAIN_LOG.warning( color_text(text="We have found no usages of Qt in " "this script despite you previously" " having imported the binding.\nIf " "you think this is in error, " "please let us know and submit an " "issue ticket with the example you " "think is wrong.", color=ANSI.colors.green)) child.parent.remove(child) continue # What we want to replace to. replace_text = "from Qt import {key}".format( key=", ".join(names)) cleaning_message = color_text(text="Cleaning", color=ANSI.colors.green) cleaning_message += ( " imports from: \"{original}\" to \"{replacement}\"") change(msg=cleaning_message, logger=MAIN_LOG, node=child, replacement=replace_text, skip_lineno=skip_lineno) child.replace(replace_text) replaced = True else: deleting_message = "{name} \"{orig}\"".format( orig=str(child).strip("\n"), name=color_text(text="Deleting", color=ANSI.colors.red)) change(msg=deleting_message, logger=MAIN_LOG, node=child, replacement="", skip_lineno=skip_lineno) child.parent.remove(child) else: pass for child in reversed(deletion_index): MAIN_LOG.debug("Deleting {node}".format(node=child)) child.parent.remove(child)
def _convert_body(red, aliases, mappings, skip_lineno=False): """ _convert_body is one of the first conversion functions to run on the redbaron ast. It finds the NameNode's or the AtomTrailersNode+DottedNameNodes and will run them through the filter expressions built off of the values in mappings. If found, it will replace the source value with the destination value in mappings. :param red: The redbaron ast. :type red: redbaron.RedBaron :param aliases: Aliases is the replacement information that is build automatically from qt_py_convert. :type aliases: dict :param mappings: Mappings is information about the bindings that are used. :type mappings: dict :param skip_lineno: An optional performance flag. By default, when the script replaces something, it will tell you which line it is replacing on. This can be useful for tracking the places that changes occurred. When you turn this flag on however, it will not show the line numbers. This can give great performance increases because redbaron has trouble calculating the line number sometimes. :type skip_lineno: bool """ def expression_factory(expr_key): """ expression_factory is a function factory for building a regex.match function for a specific key that we found in misplaced_mappings """ regex = re.compile(r"{value}(?:[\.\[\(].*)?$".format(value=expr_key), re.DOTALL) def expression_filter(value): """ Basic filter function matching for red.find_all against a regex previously created from the factory .""" return regex.match(value.dumps()) return expression_filter # Body of the function for key in sorted(mappings, key=len): MAIN_LOG.debug( color_text( text="-" * len(key), color=ANSI.colors.teal, )) MAIN_LOG.debug( color_text( text=key, color=ANSI.colors.teal, style=ANSI.styles.underline, )) if "." in key: filter_function = expression_factory(key) matches = red.find_all("AtomTrailersNode", value=filter_function) matches += red.find_all("DottedNameNode", value=filter_function) else: matches = red.find_all("NameNode", value=key) if matches: for node in matches: # Dont replace imports, we already did that. parent_is_import = node.parent_find("ImportNode") parent_is_fimport = node.parent_find("FromImportNode") if not parent_is_import and not parent_is_fimport: # If the node's parent has dot syntax. Make sure we are # the first one. Reasoning: We are relying on namespacing, # so we don't want to turn bob.foo.cat into bob.foo.bear. # Because bob.foo.cat might not be equal to the mike.cat # that we meant to change. if hasattr(node.parent, "type") and node.parent.type == "atomtrailers": if not node.parent.value[0] == node: continue if key != mappings[key]: replacement = node.dumps().replace(key, mappings[key]) change(logger=MAIN_LOG, node=node, replacement=replacement, skip_lineno=skip_lineno) if mappings[key].split(".")[0] in COMMON_MODULES: aliases["used"].add(mappings[key].split(".")[0]) node.replace(replacement) else: if node.dumps().split(".")[0] in COMMON_MODULES: aliases["used"].add(node.dumps().split(".")[0])
def _convert_attributes(red, aliases, skip_lineno=False): """ _convert_attributes converts all AtomTrailersNodes and DottenNameNodes to the Qt5/PySide2 api matching Qt.py.. This means that anything that was using QtGui but is now using QtWidgets will be updated for example. It does not do any api v1 - api v2 conversion or specific misplaced_mapping changes. :param red: The redbaron ast. :type red: redbaron.RedBaron :param aliases: Aliases is the replacement information that is build automatically from qt_py_convert. :type aliases: dict :param skip_lineno: An optional performance flag. By default, when the script replaces something, it will tell you which line it is replacing on. This can be useful for tracking the places that changes occurred. When you turn this flag on however, it will not show the line numbers. This can give great performance increases because redbaron has trouble calculating the line number sometimes. :type skip_lineno: bool """ # Compile our expressions # Our expressions are basically as follows: # From: # <Any Qt SLM>.<any_member of A> # To: # <A>.<\back reference to the member matched> # Where A is the specific Qt SecondLevelModule that we are building this # expression for. # # Also sorry this is longer than 79 chars.. # It gets harder to read the more I try to make it more readable. expressions = [ ( re.compile( r"^(?P<module>{modules})\.(?P<widget>(?:{widgets})(?:[.\[(].*)?)$" .format( # Regular expression modules="|".join( re.escape(name) for name in Qt._common_members.keys()), widgets="|".join( re.escape(widget) for widget in Qt._common_members[module_name])), re.MULTILINE), module_name) for module_name in Qt._common_members ] def finder_function_factory(exprs): """Basic function factory. Used as a find_all delegate for red.""" def finder_function(value): """The filter for our red.find_all function.""" return any( [expression.match(value.dumps()) for expression, mod in exprs]) return finder_function mappings = {} # Find any AtomTrailersNode that matches any of our expressions. nodes = red.find_all("AtomTrailersNode", value=finder_function_factory(expressions)) nodes += red.find_all("DottedNameNode", value=finder_function_factory(expressions)) header_written = False for node in nodes: orig_node_str = node.dumps() added_module = False for expr, module_ in expressions: modified = expr.sub( r"{module}.\2".format(module=module_), orig_node_str, ) if modified != orig_node_str: mappings[orig_node_str] = modified aliases["used"].add(module_) added_module = True if not header_written: MAIN_LOG.debug( color_text( text="=========================", color=ANSI.colors.orange, )) MAIN_LOG.debug( color_text(text="Parsing AtomTrailersNodes", color=ANSI.colors.orange, style=ANSI.styles.underline)) header_written = True repl = str(node).replace( str(node.value[0]).strip("\n"), module_) change(logger=Qt4_Qt5_LOG, node=node, replacement=repl, skip_lineno=skip_lineno) # Only replace the first node part of the statement. # This allows us to keep any child nodes that have already # been gathered attached to the main node tree. # This was the cause of a bug in our internal code. # http://dd-git.d2.com/ahughes/qt_py_convert/issues/19 # A node that had child nodes that needed replacements on the # same line would cause an issue if we replaced the entire # line the first replacement. The other replacements on that # line would not stick because they would be replacing to an # orphaned tree. node.value[0].replace(module_) break # else: # if orig_node_str.split(".")[0] in COMMON_MODULES: # aliases["used"].add(orig_node_str.split(".")[0]) if not added_module: aliases["used"].add(orig_node_str.split(".")[0]) return mappings