Coverage for src / renaissance / syntax_tree / recipe_ast_processor.py: 100%
82 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-09-09 14:04 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-09-09 14:04 +0000
1import functools
2from collections.abc import Callable, Sequence
3from typing import Any, TypeVar
5from .ast_processor import ASTProcessor
6from .batch_ast_processor import BatchASTProcessor, IterableProvider
8TRecipe = TypeVar("TRecipe")
9TFunc = Callable[..., Any]
12def annotate_decorator(foreign_decorator: TFunc, name: str):
13 def new_decorator(func: TFunc) -> TFunc:
14 r = foreign_decorator(func) # apply foreignDecorator, like call to foreignDecorator(method) would have done
15 r.decorator = new_decorator # keep track of decorator
16 r.recipe_action = name
17 return r
19 new_decorator.__name__ = foreign_decorator.__name__
20 new_decorator.__doc__ = foreign_decorator.__doc__
21 return new_decorator
24def get_methods_with_decorator(cls: Any, decorator: TFunc):
25 for maybe_decorated in cls.__dict__.values():
26 if hasattr(maybe_decorated, "recipe_action") and maybe_decorated.recipe_action == decorator.__name__:
27 yield maybe_decorated
30# Decorators
33def final_action() -> TFunc:
34 def final_action_decorator(func: TFunc) -> TFunc:
35 @functools.wraps(func)
36 def final_action_wrapper(recipe: TFunc):
37 func(recipe)
39 return final_action_wrapper
41 return annotate_decorator(final_action_decorator, final_action.__name__)
44def recipe_step(order: int = 0, repeat: bool = False) -> TFunc:
45 def recipe_step_decorator(func: TFunc) -> TFunc:
46 @functools.wraps(func)
47 def recipe_step_wrapper(step: int, recipe: TFunc, ast_processor: ASTProcessor):
48 if step == order and (repeat or ast_processor.repeat_step == 0):
49 result = func(recipe, ast_processor)
51 def callable_result():
52 if result:
53 result()
54 return func.__name__
56 return callable_result()
57 return None
59 return recipe_step_wrapper
61 return annotate_decorator(recipe_step_decorator, recipe_step.__name__)
64def after_step(step: str) -> TFunc:
65 def after_step_decorator(func: TFunc) -> TFunc:
66 @functools.wraps(func)
67 def after_step_wrapper(preceding_methods: Sequence[str], recipe: TFunc):
68 if step in preceding_methods:
69 func(recipe)
71 return after_step_wrapper
73 return annotate_decorator(after_step_decorator, after_step.__name__)
76class RecipeASTProcessor[TRecipe]:
77 def __init__(
78 self,
79 recipe: TRecipe,
80 iterable_provider: IterableProvider,
81 file_filter: str,
82 in_memory: bool = False,
83 max_processes: int = 4,
84 ):
85 self.__recipe: TRecipe = recipe
86 self.__batch_processor = BatchASTProcessor(in_memory=in_memory, max_processes=max_processes)
87 self.__iterableProvider = iterable_provider
88 self.__file_filter = file_filter
90 def run(self):
91 actions: list[TFunc] = []
92 results: list[Any] = []
93 for idx, recipe_step_method in enumerate(get_methods_with_decorator(type(self.__recipe), recipe_step)):
94 results.append(None)
96 def recipe_action(ast_processor: ASTProcessor):
97 result = recipe_step_method(step, self.__recipe, ast_processor)
98 if result:
99 results[idx] = result
101 actions.append(recipe_action)
103 after_step_actions: list[TFunc] = []
104 for after_step_method in get_methods_with_decorator(self.__recipe.__class__, after_step):
106 def after_step_action():
107 after_step_method(results, self.__recipe)
109 after_step_actions.append(after_step_action)
111 step = 0
112 while len(actions) > 0:
113 for idx in range(len(results)):
114 results[idx] = None
115 self.__batch_processor.repeat(self.__iterableProvider, actions, self.__file_filter)
116 if all(result is None for result in results):
117 break
118 for after_step_action in after_step_actions:
119 after_step_action()
120 step += 1
122 for method in get_methods_with_decorator(self.__recipe.__class__, final_action):
123 method(self.__recipe)