|
8 | 8 | import ast |
9 | 9 | from typing import Any |
10 | 10 | from ..tools import get_files |
| 11 | +import re |
11 | 12 |
|
12 | 13 | empty_list = ast.parse("[]").body[0].value |
13 | 14 |
|
14 | 15 |
|
| 16 | +### Abstract visitor class ### |
| 17 | + |
| 18 | + |
15 | 19 | class AbstractVisitor(ast.NodeVisitor): |
16 | | - def __init__(self) -> None: |
| 20 | + def __init__(self, all_code=None) -> None: |
17 | 21 | # ((line, line_end, col_offset, end_col_offset), replace_by) NO OVERLAPS |
| 22 | + self.all_code = all_code |
18 | 23 | self.change_todo = [] |
19 | 24 |
|
20 | 25 | def post_process(self, all_code: str, file: str) -> str: |
@@ -46,6 +51,73 @@ def add_change(self, old_node: ast.AST, new_node: ast.AST | str): |
46 | 51 | self.change_todo.append((position, ast.unparse(new_node))) |
47 | 52 |
|
48 | 53 |
|
| 54 | +def apply_visitor_scripts(logger, filename): |
| 55 | + with open(filename, mode="rt") as file: |
| 56 | + new_all = all_code = file.read() |
| 57 | + if ".read_group(" in all_code or "._read_group(" in all_code: |
| 58 | + for Step in Steps_visitor: |
| 59 | + visitor = Step() |
| 60 | + try: |
| 61 | + visitor.visit(ast.parse(new_all)) |
| 62 | + except Exception: |
| 63 | + logger.error( |
| 64 | + f"ERROR in {filename} at step {visitor.__class__}: \n{new_all}" |
| 65 | + ) |
| 66 | + raise |
| 67 | + new_all = visitor.post_process(new_all, filename) |
| 68 | + if new_all == all_code: |
| 69 | + logger.warning( |
| 70 | + "read_group detected but not changed in file %s" % filename |
| 71 | + ) |
| 72 | + else: |
| 73 | + logger.info("Script read_group replace applied in file %s" % filename) |
| 74 | + with open(filename, mode="wt") as file: |
| 75 | + file.write(new_all) |
| 76 | + |
| 77 | + if "def name_get(" in all_code: |
| 78 | + visitor = VisitorNameGet(all_code) |
| 79 | + try: |
| 80 | + visitor.visit(ast.parse(new_all)) |
| 81 | + except Exception: |
| 82 | + logger.error( |
| 83 | + f"ERROR in {filename} at step {visitor.__class__}: \n{new_all}" |
| 84 | + ) |
| 85 | + raise |
| 86 | + new_all = visitor.post_process(new_all, filename) |
| 87 | + if new_all == all_code: |
| 88 | + logger.warning( |
| 89 | + "name_get detected but not changed in file: %s" % filename |
| 90 | + ) |
| 91 | + else: |
| 92 | + logger.warning( |
| 93 | + "Script name_get replace applied in file %s. " |
| 94 | + "You probably need to add manually some dependencies to the new computed method." |
| 95 | + % filename |
| 96 | + ) |
| 97 | + with open(filename, mode="wt") as file: |
| 98 | + file.write(new_all) |
| 99 | + |
| 100 | + |
| 101 | +def _reformat_files( |
| 102 | + logger, module_path, module_name, manifest_path, migration_steps, tools |
| 103 | +): |
| 104 | + """Reformat read_group method in py files.""" |
| 105 | + |
| 106 | + reformat_file_ext = ".py" |
| 107 | + file_paths = _get_files(module_path, reformat_file_ext) |
| 108 | + logger.debug(f"{reformat_file_ext} files found:\n" f"{list(map(str, file_paths))}") |
| 109 | + |
| 110 | + reformatted_files = list() |
| 111 | + for file_path in file_paths: |
| 112 | + reformatted_file = apply_visitor_scripts(logger, file_path) |
| 113 | + if reformatted_file: |
| 114 | + reformatted_files.append(reformatted_file) |
| 115 | + logger.debug("Reformatted files:\n" f"{list(reformatted_files)}") |
| 116 | + |
| 117 | + |
| 118 | +### Refactor _read_group script ### |
| 119 | + |
| 120 | + |
49 | 121 | class VisitorToPrivateReadGroup(AbstractVisitor): |
50 | 122 | def post_process(self, all_code: str, file: str) -> str: |
51 | 123 | all_lines = all_code.split("\n") |
@@ -214,29 +286,7 @@ def visit_Call(self, node: ast.Call) -> Any: |
214 | 286 | ] |
215 | 287 |
|
216 | 288 |
|
217 | | -def replace_read_group_signature(logger, filename): |
218 | | - with open(filename, mode="rt") as file: |
219 | | - new_all = all_code = file.read() |
220 | | - if ".read_group(" in all_code or "._read_group(" in all_code: |
221 | | - for Step in Steps_visitor: |
222 | | - visitor = Step() |
223 | | - try: |
224 | | - visitor.visit(ast.parse(new_all)) |
225 | | - except Exception: |
226 | | - logger.info( |
227 | | - f"ERROR in {filename} at step {visitor.__class__}: \n{new_all}" |
228 | | - ) |
229 | | - raise |
230 | | - new_all = visitor.post_process(new_all, filename) |
231 | | - if new_all == all_code: |
232 | | - logger.info("read_group detected but not changed in file %s" % filename) |
233 | | - |
234 | | - if new_all != all_code: |
235 | | - logger.info("Script read_group replace applied in file %s" % filename) |
236 | | - with open(filename, mode="wt") as file: |
237 | | - file.write(new_all) |
238 | | - |
239 | | - |
| 289 | +### Warning open_form_view ### |
240 | 290 |
|
241 | 291 |
|
242 | 292 | def _check_open_form_view(logger, file_path: Path): |
@@ -266,23 +316,64 @@ def _check_open_form( |
266 | 316 | _check_open_form_view(logger, file_path) |
267 | 317 |
|
268 | 318 |
|
269 | | -def _reformat_read_group( |
270 | | - logger, module_path, module_name, manifest_path, migration_steps, tools |
271 | | -): |
272 | | - """Reformat read_group method in py files.""" |
| 319 | +### name_get script ### |
273 | 320 |
|
274 | | - reformat_file_ext = (".py") |
275 | | - file_paths = get_files(module_path, reformat_file_ext) |
276 | | - logger.debug(f"{reformat_file_ext} files found:\n" f"{list(map(str, file_paths))}") |
| 321 | +list_comprehension_re = re.compile( |
| 322 | + r"""def name_get\(self\):(.*) |
| 323 | + return \[\s*\(.*id,\s*(.+)\s*\)\s*for\s*(\w+)\s*in\s*self\s*\]""", |
| 324 | + re.S, |
| 325 | +) |
| 326 | +list_comprehension_into = r"""@api.depends('') |
| 327 | + def _compute_display_name(self):\g<1> |
| 328 | + for \g<3> in self: |
| 329 | + \g<3>.display_name = \g<2>""" |
277 | 330 |
|
278 | | - reformatted_files = list() |
279 | | - for file_path in file_paths: |
280 | | - reformatted_file = replace_read_group_signature(logger, file_path) |
281 | | - if reformatted_file: |
282 | | - reformatted_files.append(reformatted_file) |
283 | | - logger.debug("Reformatted files:\n" f"{list(reformatted_files)}") |
| 331 | +list_append_re = re.compile( |
| 332 | + r"""def name_get\(self\):.+ |
| 333 | + return ([A-Za-z_]+)""", |
| 334 | + re.S, |
| 335 | +) |
| 336 | + |
| 337 | + |
| 338 | +class VisitorNameGet(AbstractVisitor): |
| 339 | + def post_process(self, all_code: str, file: str) -> str: |
| 340 | + for (old_str, new_str) in self.change_todo: |
| 341 | + all_code = all_code.replace(old_str, new_str) |
| 342 | + return all_code |
| 343 | + |
| 344 | + def add_change(self, old_str, new_str): |
| 345 | + self.change_todo.append((old_str, new_str)) |
| 346 | + |
| 347 | + def visit_FunctionDef(self, node: ast.FunctionDef) -> Any: |
| 348 | + if node.name == "name_get": |
| 349 | + # Replace fields by aggregate and orderby by order |
| 350 | + code = ast.get_source_segment(self.all_code, node) |
| 351 | + new_code = list_comprehension_re.sub(list_comprehension_into, code, 1) |
| 352 | + |
| 353 | + if new_code != code: |
| 354 | + self.add_change(code, new_code) |
| 355 | + elif groups := list_append_re.fullmatch(new_code): |
| 356 | + variable_to_remove = groups.group(1) |
| 357 | + new_code = new_code.replace(f"\n {variable_to_remove} = []", "") |
| 358 | + new_code = new_code.replace( |
| 359 | + "def name_get(self):", "def _compute_display_name(self):" |
| 360 | + ) |
| 361 | + new_code = new_code.replace( |
| 362 | + "super().name_get()", "super()._compute_display_name()" |
| 363 | + ) |
| 364 | + new_code = new_code.replace( |
| 365 | + f"\n return {variable_to_remove}", "" |
| 366 | + ) |
| 367 | + new_code = re.sub( |
| 368 | + variable_to_remove + r".append\(\((.+).id, (.*),?\)\)", |
| 369 | + r"\g<1>.display_name = \g<2>", |
| 370 | + new_code, |
| 371 | + ) |
| 372 | + self.add_change(code, new_code) |
| 373 | + else: |
| 374 | + print(f"Cannot find a way to convert:\n{code}") |
284 | 375 |
|
285 | 376 |
|
286 | 377 | class MigrationScript(BaseMigrationScript): |
287 | 378 |
|
288 | | - _GLOBAL_FUNCTIONS = [_check_open_form, _reformat_read_group] |
| 379 | + _GLOBAL_FUNCTIONS = [_check_open_form, _reformat_files] |
0 commit comments