diff --git a/vupdate.py b/vupdate.py index 9dc53f0..942081f 100644 --- a/vupdate.py +++ b/vupdate.py @@ -27,6 +27,11 @@ from resources.lib import logger from resources.lib.logger import VSlog, VSPath +from io import StringIO + +# Assuming the unparser code is in a module/module named unparser +from resources.lib.unparser import Unparser # Import the Unparser class from the script + def insert_update_service_addon(): """ Opens the file at @@ -1943,540 +1948,16 @@ def visit_Str(self, node): return ast.copy_location(ast.Str(s=safe_pattern), node) return node -###################################### -# Fallback AST Unparser Function -###################################### - -def my_unparse(node, depth=0, max_depth=50, indent_level=0): - """ - A robust fallback AST unparser for AST nodes with corrected indentation. - """ - def format_body(statements, current_indent): - """Formats the body with proper indentation.""" - body_lines = [] - for stmt in statements: - unparsed = my_unparse(stmt, depth + 1, max_depth, current_indent) - for line in unparsed.split('\n'): - body_lines.append(' ' * current_indent + line) - if not body_lines: - body_lines.append(' ' * current_indent + 'pass') - return '\n'.join(body_lines) - - try: - if depth > max_depth: - return ast.dump(node) - - # Module: Top-level, no indentation - if isinstance(node, ast.Module): - return "\n".join(my_unparse(stmt, depth + 1, max_depth, indent_level) for stmt in node.body) - - # Function definition: body indented by +1 level - elif isinstance(node, ast.FunctionDef): - decorators = [f"@{my_unparse(d, depth + 1, max_depth, indent_level)}" for d in node.decorator_list] - decorator_str = "\n".join(decorators) + "\n" if decorators else "" - args = my_unparse(node.args, depth + 1, max_depth, indent_level) - body = format_body(node.body, indent_level + 1) - return f"{decorator_str}def {node.name}({args}):\n{body}" - - # Class definition: body indented by +1 level - elif isinstance(node, ast.ClassDef): - decorators = [f"@{my_unparse(d, depth + 1, max_depth, indent_level)}" for d in node.decorator_list] - bases = [my_unparse(b, depth + 1, max_depth, indent_level) for b in node.bases] - keywords = [my_unparse(kw, depth + 1, max_depth, indent_level) for kw in node.keywords] - body = format_body(node.body, indent_level + 1) - return "\n".join(decorators) + f"\nclass {node.name}({', '.join(bases + keywords)}):\n{body}" - - # If statement: body indented by +1 level - elif isinstance(node, ast.If): - test = my_unparse(node.test, depth + 1, max_depth, indent_level) - body = format_body(node.body, indent_level + 1) - orelse = "" - if node.orelse: - if len(node.orelse) == 1 and isinstance(node.orelse[0], ast.If): - elif_stmt = my_unparse(node.orelse[0], depth + 1, max_depth, indent_level).replace("if", "elif", 1) - return f"if {test}:\n{body}\n{elif_stmt}" - else: - orelse = format_body(node.orelse, indent_level + 1) - return f"if {test}:\n{body}\nelse:\n{orelse}" - return f"if {test}:\n{body}" - - # For loop: body indented by +1 level - elif isinstance(node, ast.For): - target = my_unparse(node.target, depth + 1, max_depth, indent_level) - iter_ = my_unparse(node.iter, depth + 1, max_depth, indent_level) - body = format_body(node.body, indent_level + 1) - orelse = format_body(node.orelse, indent_level + 1) if node.orelse else "" - return f"for {target} in {iter_}:\n{body}" + (f"\nelse:\n{orelse}" if orelse else "") - - # Ternary operator - elif isinstance(node, ast.IfExp): - test = my_unparse(node.test, depth+1, max_depth) - body = my_unparse(node.body, depth+1, max_depth) - orelse = my_unparse(node.orelse, depth+1, max_depth) - return f"({body} if {test} else {orelse})" - - # Function definitions - elif isinstance(node, ast.FunctionDef): - decorators = [f"@{my_unparse(d, depth+1, max_depth)}" for d in node.decorator_list] - decorator_str = "\n".join(decorators) + "\n" if decorators else "" - args = my_unparse(node.args, depth+1, max_depth) - body = format_body(node.body, depth) - return f"{decorator_str}def {node.name}({args}):\n{body}" - - # Class definitions - elif isinstance(node, ast.ClassDef): - decorators = [f"@{my_unparse(d, depth+1, max_depth)}" for d in node.decorator_list] - bases = [my_unparse(b, depth+1, max_depth) for b in node.bases] - keywords = [my_unparse(kw, depth+1, max_depth) for kw in node.keywords] - body = format_body(node.body, depth) - return "\n".join(decorators) + f"\nclass {node.name}({', '.join(bases + keywords)}):\n{body}" - - # Parameter handling - elif isinstance(node, ast.arguments): - params = [] - pos_only = [my_unparse(arg, depth+1, max_depth) for arg in node.posonlyargs] - if pos_only: - params.extend(pos_only) - if node.args or node.vararg or node.kwonlyargs or node.kwarg: - params.append('/') - - args = node.args - defaults = node.defaults - num_defaults = len(defaults) - for i, arg in enumerate(args): - arg_str = my_unparse(arg, depth+1, max_depth) - if i >= len(args) - num_defaults: - default = defaults[i - (len(args) - num_defaults)] - arg_str += f"={my_unparse(default, depth+1, max_depth)}" - params.append(arg_str) - - if node.vararg: - vararg = my_unparse(node.vararg, depth+1, max_depth) - if getattr(node.vararg, 'annotation', None): - vararg += f": {my_unparse(node.vararg.annotation, depth+1, max_depth)}" - params.append(f"*{vararg}") - elif node.kwonlyargs: - params.append('*') - - for i, kwarg in enumerate(node.kwonlyargs): - kwarg_str = my_unparse(kwarg, depth+1, max_depth) - if i < len(node.kw_defaults): - default = node.kw_defaults[i] - if default: - kwarg_str += f"={my_unparse(default, depth+1, max_depth)}" - params.append(kwarg_str) - - if node.kwarg: - kwarg = my_unparse(node.kwarg, depth+1, max_depth) - if getattr(node.kwarg, 'annotation', None): - kwarg += f": {my_unparse(node.kwarg.annotation, depth+1, max_depth)}" - params.append(f"**{kwarg}") - - return ", ".join(params) - - # Parameter with type annotation - elif isinstance(node, ast.arg): - annotation = f": {my_unparse(node.annotation, depth+1, max_depth)}" if node.annotation else "" - return f"{node.arg}{annotation}" - - # Assignment statements - elif isinstance(node, ast.Assign): - targets = " = ".join(my_unparse(t, depth+1, max_depth) for t in node.targets) - value = my_unparse(node.value, depth+1, max_depth) - return f"{targets} = {value}" - - # Expression statements - elif isinstance(node, ast.Expr): - return my_unparse(node.value, depth+1, max_depth) - - # Function/method calls - elif isinstance(node, ast.Call): - func = my_unparse(node.func, depth+1, max_depth) - args = [my_unparse(a, depth+1, max_depth) for a in node.args] - keywords = [my_unparse(k, depth+1, max_depth) for k in node.keywords] - return f"{func}({', '.join(args + keywords)})" - - # Keyword arguments (e.g., func(arg=value)) - elif isinstance(node, ast.keyword): - if node.arg is None: - return f"**{my_unparse(node.value, depth+1, max_depth)}" - else: - return f"{node.arg}={my_unparse(node.value, depth+1, max_depth)}" - - # Variable names - elif isinstance(node, ast.Name): - return node.id - - # Constants - elif isinstance(node, ast.Constant): - if node.value is Ellipsis: - return "..." - return repr(node.value) - - # String literals (Python <3.8) - elif isinstance(node, ast.Str): - return repr(node.s) - - # Return statements - elif isinstance(node, ast.Return): - if node.value is not None: - value = my_unparse(node.value, depth+1, max_depth) - return f"return {value}" - else: - return "return" - - # Binary operations - elif isinstance(node, ast.BinOp): - left = my_unparse(node.left, depth+1, max_depth) - op = my_unparse(node.op, depth+1, max_depth) - right = my_unparse(node.right, depth+1, max_depth) - return f"({left} {op} {right})" - - # Math operators - elif isinstance(node, ast.Add): return "+" - elif isinstance(node, ast.Sub): return "-" - elif isinstance(node, ast.Mult): return "*" - elif isinstance(node, ast.Div): return "/" - elif isinstance(node, ast.FloorDiv): return "//" - elif isinstance(node, ast.Mod): return "%" - elif isinstance(node, ast.Pow): return "**" - - # Imports - elif isinstance(node, ast.Import): - names = ", ".join( - f"{alias.name} as {alias.asname}" if alias.asname else alias.name - for alias in node.names - ) - return f"import {names}" - - elif isinstance(node, ast.ImportFrom): - module = ("." * node.level) + (node.module or "") - names = ", ".join( - f"{alias.name} as {alias.asname}" if alias.asname else alias.name - for alias in node.names - ) - return f"from {module} import {names}" - - # Control structures - elif isinstance(node, ast.If): - test = my_unparse(node.test, depth+1, max_depth) - body = format_body(node.body, depth) - if node.orelse: - if len(node.orelse) == 1 and isinstance(node.orelse[0], ast.If): - elif_stmt = my_unparse(node.orelse[0], depth+1, max_depth).replace("if", "elif", 1) - return f"if {test}:\n{body}\n{elif_stmt}" - else: - orelse = format_body(node.orelse, depth) - return f"if {test}:\n{body}\nelse:\n{orelse}" - return f"if {test}:\n{body}" - - elif isinstance(node, ast.For): - target = my_unparse(node.target, depth+1, max_depth) - iter_ = my_unparse(node.iter, depth+1, max_depth) - body = format_body(node.body, depth) - orelse = "" - if node.orelse: - orelse = f"\nelse:\n{format_body(node.orelse, depth)}" - return f"for {target} in {iter_}:\n{body}{orelse}" - - elif isinstance(node, ast.While): - test = my_unparse(node.test, depth+1, max_depth) - body = format_body(node.body, depth) - orelse = "" - if node.orelse: - orelse = f"\nelse:\n{format_body(node.orelse, depth)}" - return f"while {test}:\n{body}{orelse}" - - elif isinstance(node, ast.With): - items = [] - for item in node.items: - ctx_expr = my_unparse(item.context_expr, depth+1, max_depth) - var = f" as {my_unparse(item.optional_vars, depth+1, max_depth)}" if item.optional_vars else "" - items.append(f"{ctx_expr}{var}") - body = format_body(node.body, depth) - return f"with {', '.join(items)}:\n{body}" - - # Comparisons - elif isinstance(node, ast.Compare): - left = my_unparse(node.left, depth+1, max_depth) - ops = [my_unparse(op, depth+1, max_depth) for op in node.ops] - comparators = [my_unparse(c, depth+1, max_depth) for c in node.comparators] - return f"{left} {' '.join(f'{op} {comp}' for op, comp in zip(ops, comparators))}" - - elif isinstance(node, ast.Eq): return "==" - elif isinstance(node, ast.NotEq): return "!=" - elif isinstance(node, ast.Lt): return "<" - elif isinstance(node, ast.LtE): return "<=" - elif isinstance(node, ast.Gt): return ">" - elif isinstance(node, ast.GtE): return ">=" - elif isinstance(node, ast.Is): return "is" - elif isinstance(node, ast.IsNot): return "is not" - elif isinstance(node, ast.In): return "in" - elif isinstance(node, ast.NotIn): return "not in" - - # Boolean logic - elif isinstance(node, ast.BoolOp): - op_str = " and " if isinstance(node.op, ast.And) else " or " - return f"({op_str.join(my_unparse(v, depth+1, max_depth) for v in node.values)})" - - # Unary operations - elif isinstance(node, ast.UnaryOp): - op = my_unparse(node.op, depth+1, max_depth) - operand = my_unparse(node.operand, depth+1, max_depth) - space = " " if op == "not" else "" - return f"{op}{space}{operand}" - - elif isinstance(node, ast.Not): return "not" - elif isinstance(node, ast.USub): return "-" - elif isinstance(node, ast.UAdd): return "+" - elif isinstance(node, ast.Invert): return "~" - - # Data structures - elif isinstance(node, ast.Dict): - pairs = [] - for k, v in zip(node.keys, node.values): - if k is None: - pairs.append(f"**{my_unparse(v, depth+1, max_depth)}") - else: - k_str = my_unparse(k, depth+1, max_depth) - v_str = my_unparse(v, depth+1, max_depth) - pairs.append(f"{k_str}: {v_str}") - return "{" + ", ".join(pairs) + "}" - - elif isinstance(node, ast.List): - elements = ", ".join(my_unparse(e, depth+1, max_depth) for e in node.elts) - return f"[{elements}]" - - elif isinstance(node, ast.Tuple): - elements = ", ".join(my_unparse(e, depth+1, max_depth) for e in node.elts) - if len(node.elts) == 1: - elements += "," - return f"({elements})" - - elif isinstance(node, ast.Set): - elements = ", ".join(my_unparse(e, depth+1, max_depth) for e in node.elts) - return f"{{{elements}}}" if elements else "set()" - - # Lambda expressions - elif isinstance(node, ast.Lambda): - args = my_unparse(node.args, depth+1, max_depth) - body = my_unparse(node.body, depth+1, max_depth) - return f"lambda {args}: {body}" - - # Comprehensions - elif isinstance(node, ast.ListComp): - elt = my_unparse(node.elt, depth+1, max_depth) - gens = " ".join(my_unparse(g, depth+1, max_depth) for g in node.generators) - return f"[{elt} {gens}]" - - elif isinstance(node, ast.SetComp): - elt = my_unparse(node.elt, depth+1, max_depth) - gens = " ".join(my_unparse(g, depth+1, max_depth) for g in node.generators) - return f"{{{elt} {gens}}}" - - elif isinstance(node, ast.DictComp): - key = my_unparse(node.key, depth+1, max_depth) - value = my_unparse(node.value, depth+1, max_depth) - gens = " ".join(my_unparse(g, depth+1, max_depth) for g in node.generators) - return f"{{{key}: {value} {gens}}}" - - elif isinstance(node, ast.GeneratorExp): - elt = my_unparse(node.elt, depth+1, max_depth) - gens = " ".join(my_unparse(g, depth+1, max_depth) for g in node.generators) - return f"({elt} {gens})" - - elif isinstance(node, ast.comprehension): - target = my_unparse(node.target, depth+1, max_depth) - iter_ = my_unparse(node.iter, depth+1, max_depth) - ifs = [my_unparse(cond, depth+1, max_depth) for cond in node.ifs] - ifs_str = " if " + " if ".join(ifs) if ifs else "" - return f"for {target} in {iter_}{ifs_str}" - - # Exception handling - elif isinstance(node, ast.Try): - try_body = format_body(node.body, depth) - excepts = [] - for handler in node.handlers: - type_str = my_unparse(handler.type, depth+1, max_depth) if handler.type else "" - name = f" as {handler.name}" if handler.name else "" - except_parts = [] - if type_str: - except_parts.append(type_str) - if name: - except_parts.append(name) - except_clause = ' '.join(except_parts) - if except_clause: - except_clause = ' ' + except_clause - else: - except_clause = '' - handler_body = format_body(handler.body, depth) - excepts.append(f"except{except_clause}:\n{handler_body}") - else_body = "" - if node.orelse: - else_body = f"\nelse:\n{format_body(node.orelse, depth)}" - finally_body = "" - if node.finalbody: - finally_body = f"\nfinally:\n{format_body(node.finalbody, depth)}" - return f"try:\n{try_body}\n" + "\n".join(excepts) + else_body + finally_body - - # Async constructs - elif isinstance(node, ast.AsyncFunctionDef): - decorators = [f"@{my_unparse(d, depth+1, max_depth)}" for d in node.decorator_list] - decorator_str = "\n".join(decorators) + "\n" if decorators else "" - args = my_unparse(node.args, depth+1, max_depth) - body = format_body(node.body, depth) - return f"{decorator_str}async def {node.name}({args}):\n{body}" - - elif isinstance(node, ast.AsyncFor): - target = my_unparse(node.target, depth+1, max_depth) - iter_ = my_unparse(node.iter, depth+1, max_depth) - body = format_body(node.body, depth) - orelse = "" - if node.orelse: - orelse = f"\nelse:\n{format_body(node.orelse, depth)}" - return f"async for {target} in {iter_}:\n{body}{orelse}" - - elif isinstance(node, ast.AsyncWith): - items = [] - for item in node.items: - ctx_expr = my_unparse(item.context_expr, depth+1, max_depth) - var = f" as {my_unparse(item.optional_vars, depth+1, max_depth)}" if item.optional_vars else "" - items.append(f"{ctx_expr}{var}") - body = format_body(node.body, depth) - return f"async with {', '.join(items)}:\n{body}" - - elif isinstance(node, ast.Await): - value = my_unparse(node.value, depth+1, max_depth) - return f"await {value}" - - # Type annotations - elif isinstance(node, ast.AnnAssign): - target = my_unparse(node.target, depth+1, max_depth) - annotation = my_unparse(node.annotation, depth+1, max_depth) - value = f" = {my_unparse(node.value, depth+1, max_depth)}" if node.value else "" - return f"{target}: {annotation}{value}" - - # Walrus operator - elif isinstance(node, ast.NamedExpr): - target = my_unparse(node.target, depth+1, max_depth) - value = my_unparse(node.value, depth+1, max_depth) - return f"({target} := {value})" - - # Formatted strings - elif isinstance(node, ast.JoinedStr): - parts = [my_unparse(v, depth+1, max_depth) for v in node.values] - return f"f{''.join(parts)}" - - elif isinstance(node, ast.FormattedValue): - value = my_unparse(node.value, depth+1, max_depth) - conversion = f"!{chr(node.conversion)}" if node.conversion != -1 else "" - format_spec = f":{my_unparse(node.format_spec, depth+1, max_depth)}" if node.format_spec else "" - return f"{{{value}{conversion}{format_spec}}}" - - # Yield statements - elif isinstance(node, ast.Yield): - value = my_unparse(node.value, depth+1, max_depth) if node.value else "" - return f"yield {value}" if value else "yield" - - elif isinstance(node, ast.YieldFrom): - value = my_unparse(node.value, depth+1, max_depth) - return f"yield from {value}" - - # Control statements - elif isinstance(node, ast.Global): - return f"global {', '.join(node.names)}" - - elif isinstance(node, ast.Nonlocal): - return f"nonlocal {', '.join(node.names)}" - - elif isinstance(node, ast.Assert): - test = my_unparse(node.test, depth+1, max_depth) - msg = f", {my_unparse(node.msg, depth+1, max_depth)}" if node.msg else "" - return f"assert {test}{msg}" - - elif isinstance(node, ast.Raise): - expr = my_unparse(node.exc, depth+1, max_depth) if node.exc else "" - cause = f" from {my_unparse(node.cause, depth+1, max_depth)}" if node.cause else "" - return f"raise {expr}{cause}" if expr or cause else "raise" - - elif isinstance(node, ast.Pass): - return "pass" - - elif isinstance(node, ast.Break): - return "break" - - elif isinstance(node, ast.Continue): - return "continue" - - # Advanced operators - elif isinstance(node, ast.BitAnd): return "&" - elif isinstance(node, ast.BitOr): return "|" - elif isinstance(node, ast.BitXor): return "^" - elif isinstance(node, ast.LShift): return "<<" - elif isinstance(node, ast.RShift): return ">>" - - # Augmented assignment - elif isinstance(node, ast.AugAssign): - target = my_unparse(node.target, depth+1, max_depth) - op = my_unparse(node.op, depth+1, max_depth) - value = my_unparse(node.value, depth+1, max_depth) - return f"{target} {op}= {value}" - - # Subscripting - elif isinstance(node, ast.Subscript): - value = my_unparse(node.value, depth+1, max_depth) - slice_part = my_unparse(node.slice, depth+1, max_depth) - return f"{value}[{slice_part}]" - - elif isinstance(node, ast.Slice): - lower = my_unparse(node.lower, depth+1, max_depth) if node.lower else "" - upper = my_unparse(node.upper, depth+1, max_depth) if node.upper else "" - step = my_unparse(node.step, depth+1, max_depth) if node.step else "" - return f"{lower}:{upper}" + (f":{step}" if step else "") - - elif isinstance(node, ast.Index): - return my_unparse(node.value, depth+1, max_depth) - - # Starred expressions - elif isinstance(node, ast.Starred): - value = my_unparse(node.value, depth+1, max_depth) - return f"*{value}" - - # Attribute access - elif isinstance(node, ast.Attribute): - value = my_unparse(node.value, depth+1, max_depth) - return f"{value}.{node.attr}" - - # Pattern matching (Python 3.10+) - elif isinstance(node, ast.Match): - subject = my_unparse(node.subject, depth+1, max_depth) - cases = "\n".join(my_unparse(c, depth+1, max_depth) for c in node.cases) - return f"match {subject}:\n{cases}" - - elif isinstance(node, ast.match_case): - pattern = my_unparse(node.pattern, depth+1, max_depth) - guard = f" if {my_unparse(node.guard, depth+1, max_depth)}" if node.guard else "" - body = format_body(node.body, depth) - return f"case {pattern}{guard}:\n{body}" - - # Edge cases - elif isinstance(node, ast.Set) and not node.elts: - return "set()" - - elif isinstance(node, ast.Expr) and isinstance(node.value, ast.Str): - return f'"""{node.value.s}"""' - - # Final fallback - else: - return ast.dump(node) - - except Exception as e: - return ast.dump(node) - ###################################### # File Rewriting & CLI Handling ###################################### +def convert_ast_to_code(tree): + """Helper function to unparse AST using the custom Unparser""" + buffer = StringIO() + Unparser(tree, file=buffer) + return buffer.getvalue() + def rewrite_file_to_avoid_regex_infinite_loops(file_path, dry_run=False, backup=False): """ Rewrites the given file to avoid infinite loops in regular expressions. @@ -2497,9 +1978,11 @@ def rewrite_file_to_avoid_regex_infinite_loops(file_path, dry_run=False, backup= if hasattr(ast, "unparse"): new_code = ast.unparse(new_tree) + else hasattr(unparse, "unparse"): + astunparse.unparse(new_tree) else: - VSlog("ast.unparse() not available; using custom unparser.") - new_code = my_unparse(new_tree) + VSlog("ast.unparse() and astunparse() not available; using custom unparser.") + new_code = convert_ast_to_code(tree) try: compile(new_code, file_path, 'exec')