Coverage for src / renaissance / integrations / python / ast / factory.py: 98%
110 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 ast
2import re
3from collections.abc import Sequence
4from pathlib import Path
6import tree_sitter_python
7from libcst import SimpleStatementLine
9from renaissance.integrations import MATCH_ALL, MATCH_ONE
10from renaissance.integrations.python.ast.ast_node import ASTExtension
11from renaissance.integrations.python.ast.cst_node import PythonCstNode
12from renaissance.integrations.python.ast.rst_node import PythonRstNode
13from renaissance.integrations.tree_sitter.adapter import TreeSitterAdapter
14from renaissance.integrations.tree_sitter.lst import LSTNode
15from renaissance.integrations.types import Arg, DeclarationExpression, ExpressionStatement, MatchAll, MatchOne, Name, Type
16from renaissance.syntax_tree.match_finder import AstProtocol, is_match
17from renaissance.utils.ast_utils import replace_dollar, use_dollar
19_MATCH_ALL_RE = re.compile(r"^" + re.escape(MATCH_ALL) + r"\w+$")
20_MATCH_ONE_RE = re.compile(r"^" + re.escape(MATCH_ONE) + r"\w+$")
22SHOW_NODE = False
25class PythonPattern(AstProtocol):
26 def __init__(self, node):
27 self.node: PythonRstNode = node
28 if type(node) is str:
29 print(node)
30 return
31 self.ast_type: Type = self.derive_type(node)
33 self.properties: dict = node.properties
34 self.children: list[PythonPattern] = [PythonPattern(node) for node in node.children]
35 self.signature: str = node.signature
36 if hasattr(node, "name") and node.name:
37 self.name: str = use_dollar(node.name)
38 else:
39 self.name = ""
41 def __eq__(self, other: AstProtocol) -> bool:
42 return is_match(other, self)
44 def __repr__(self):
45 return use_dollar(str(self.node))
47 def derive_type(self, node) -> str:
48 # signature = ""
49 # if isinstance(node.ast_type(), Argument):
50 # signature = node.node.arg
51 # elif isinstance(node.ast_type(), Name):
52 # signature = node.node.value
53 # elif isinstance(node.ast_type(), ExpressionStatement) and isinstance(node.node.value, ast.Name):
54 # signature = node.node.value.id
55 # if _MATCH_ALL_RE.match(signature):
56 # return MatchAll
57 # elif _MATCH_ONE_RE.match(signature):
58 # return MatchOne
59 # if isinstance(node, LSTNode):
60 # return node.ast_type
61 # else:
62 # return node.ast_type
63 if isinstance(node, ast.arg):
64 signature = node.arg
65 elif isinstance(node, ast.Name):
66 signature = node.id
67 elif isinstance(node, ast.Expr) and isinstance(node.value, ast.Name):
68 signature = node.value.id
69 elif isinstance(node, ast.AST):
70 signature = str(node)
71 else:
72 signature = node.name
74 if node.ast_type in [DeclarationExpression, ExpressionStatement, Name, Arg]:
75 if _MATCH_ALL_RE.match(signature):
76 return MatchAll
77 if _MATCH_ONE_RE.match(signature):
78 return MatchOne
80 return node.ast_type
83class PythonFactory:
84 def __init__(self, clazz: type[PythonRstNode | PythonCstNode | LSTNode | ast.AST]) -> None:
85 self.clazz = clazz
86 if clazz == LSTNode:
87 clazz.load_from_text = self.load_from_lst
88 elif clazz == ast.AST:
89 clazz.load_from_text = ASTExtension.load_from_ast
90 # matcher
91 clazz.node = ASTExtension.ast_node
93 # clazz.name = ASTExtension.ast_name
94 clazz.ast_type = ASTExtension.ast_type
95 clazz.properties = ASTExtension.ast_properties
96 clazz.children = ASTExtension.ast_children
97 clazz.signature = ASTExtension.ast_signature
99 # writer
100 clazz.text = ASTExtension.ast_signature
101 clazz.filename = "dummy.py"
103 # shower
104 clazz.is_implicit = True
105 clazz.show_props = False
106 clazz.indent = ""
108 def create(self, file_path: Path) -> PythonRstNode | PythonCstNode:
109 atu = self.clazz.load(file_path=file_path)
110 assert isinstance(atu, self.clazz)
111 return atu
113 def create_from_text(self, text: str, file_name: str = "snippet.py") -> PythonRstNode | PythonCstNode | LSTNode | ast.AST:
114 atu = self.clazz.load_from_text(text, file_name)
115 assert isinstance(atu, self.clazz)
116 return atu
118 @staticmethod
119 def load_from_lst(text, file):
120 adapter = TreeSitterAdapter(tree_sitter_python)
121 tree = adapter.parse_code(text)
122 return adapter.to_lst(text, tree).root
125class PythonPatternFactory:
126 def __init__(self, factory: PythonFactory):
127 self.factory = factory
129 def _create(self, text: str) -> PythonPattern:
130 return PythonPattern(self.factory.create_from_text(text, "pattern.py"))
132 def create(self, text: str) -> PythonPattern:
133 text = replace_dollar(text)
134 return self._create(text)
136 def create_statements(self, text: str) -> Sequence[PythonPattern]:
137 atu = self.create(text)
138 return atu.children
140 def create_statement(self, text: str) -> PythonPattern:
141 stmt = self.create_statements(text)[-1]
142 if isinstance(stmt.node.node, SimpleStatementLine):
143 return stmt.children[0]
144 return stmt
145 # return stmt
147 def create_expression(self, text: str) -> PythonPattern:
148 my_pattern = self.create_statement(text)
149 if isinstance(my_pattern.node, PythonRstNode):
150 return PythonPattern(my_pattern.node.expression)
151 if isinstance(my_pattern.node, (LSTNode, PythonCstNode)):
152 return PythonPattern(my_pattern.node.children[-1])
153 return PythonPattern(my_pattern.node.children[0])
155 def create_decorators(self, param):
156 return self.create_statement(param + "\ndef test(): pass").children[2]
158 @staticmethod
159 def create_kwargs(kw_str) -> Sequence[PythonPattern]:
160 call = ast.parse(f"fun({replace_dollar(kw_str)})", "kwarg_pattern.py", type_comments=True).body[0].value
161 return [PythonPattern(PythonRstNode(kwarg)) for kwarg in call.keywords]