mapFolding 0.8.0__py3-none-any.whl → 0.8.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. mapFolding/__init__.py +33 -4
  2. mapFolding/basecamp.py +14 -0
  3. mapFolding/beDRY.py +16 -1
  4. mapFolding/filesystem.py +124 -90
  5. mapFolding/noHomeYet.py +12 -0
  6. mapFolding/oeis.py +16 -1
  7. mapFolding/reference/__init__.py +0 -0
  8. mapFolding/reference/flattened.py +46 -45
  9. mapFolding/reference/hunterNumba.py +4 -4
  10. mapFolding/reference/irvineJavaPort.py +1 -1
  11. mapFolding/reference/lunnanNumpy.py +3 -4
  12. mapFolding/reference/lunnanWhile.py +5 -7
  13. mapFolding/reference/rotatedEntryPoint.py +2 -3
  14. mapFolding/someAssemblyRequired/__init__.py +29 -0
  15. mapFolding/someAssemblyRequired/getLLVMforNoReason.py +32 -14
  16. mapFolding/someAssemblyRequired/ingredientsNumba.py +22 -1
  17. mapFolding/someAssemblyRequired/synthesizeNumbaFlow.py +196 -0
  18. mapFolding/someAssemblyRequired/synthesizeNumbaJobVESTIGIAL.py +3 -4
  19. mapFolding/someAssemblyRequired/transformDataStructures.py +162 -0
  20. mapFolding/someAssemblyRequired/transformationTools.py +216 -199
  21. mapFolding/theDao.py +19 -5
  22. mapFolding/theSSOT.py +19 -1
  23. {mapfolding-0.8.0.dist-info → mapfolding-0.8.1.dist-info}/METADATA +50 -44
  24. mapfolding-0.8.1.dist-info/RECORD +39 -0
  25. {mapfolding-0.8.0.dist-info → mapfolding-0.8.1.dist-info}/WHEEL +1 -1
  26. mapFolding/reference/lunnan.py +0 -153
  27. mapFolding/someAssemblyRequired/Z0Z_workbench.py +0 -350
  28. mapFolding/someAssemblyRequired/synthesizeDataConverters.py +0 -117
  29. mapFolding/syntheticModules/numbaCountHistoricalExample.py +0 -158
  30. mapFolding/syntheticModules/numba_doTheNeedfulHistoricalExample.py +0 -13
  31. mapfolding-0.8.0.dist-info/RECORD +0 -41
  32. {mapfolding-0.8.0.dist-info → mapfolding-0.8.1.dist-info}/entry_points.txt +0 -0
  33. {mapfolding-0.8.0.dist-info → mapfolding-0.8.1.dist-info/licenses}/LICENSE +0 -0
  34. {mapfolding-0.8.0.dist-info → mapfolding-0.8.1.dist-info}/top_level.txt +0 -0
@@ -1,20 +1,36 @@
1
- """As of 2025-03-15
2
- Tools for transforming Python code from one format to another.
3
-
4
- Scope:
5
- - What is necessary to transform the baseline algorithm into optimized formats used by the official package.
6
-
7
- Aspirations:
8
- - Each tool is abstracted or generic enough to be used beyond the scope of the official package.
9
- - Each tool is designed to be used in a modular fashion, allowing for the creation of new tools by combining existing tools.
10
- - If a tool has a default setting, the setting shall be the setting used by the official package.
11
1
  """
2
+ Tools for transforming Python code through abstract syntax tree (AST) manipulation.
3
+
4
+ This module provides a comprehensive set of utilities for programmatically analyzing,
5
+ transforming, and generating Python code through AST manipulation. It implements
6
+ a highly flexible framework that enables:
7
+
8
+ 1. Precise identification of code patterns through composable predicates
9
+ 2. Targeted modification of code structures while preserving semantics
10
+ 3. Code generation with proper syntax and import management
11
+ 4. Analysis of code dependencies and relationships
12
+ 5. Clean transformation of one algorithmic implementation to another
13
+
14
+ The utilities are organized into several key components:
15
+ - Predicate factories (ifThis): Create composable functions for matching AST patterns
16
+ - Node transformers: Modify AST structures in targeted ways
17
+ - Code generation helpers (Make): Create well-formed AST nodes programmatically
18
+ - Import tracking: Maintain proper imports during code transformation
19
+ - Analysis tools: Extract and organize code information
20
+
21
+ While these tools were developed to transform the baseline algorithm into optimized formats,
22
+ they are designed as general-purpose utilities applicable to a wide range of code
23
+ transformation scenarios beyond the scope of this package.
24
+ """
25
+ from autoflake import fix_code as autoflake_fix_code
12
26
  from collections import defaultdict
13
- from collections.abc import Callable, Container, Generator, Sequence
14
- from contextlib import contextmanager
27
+ from collections.abc import Callable, Container, Sequence
28
+ from copy import deepcopy
15
29
  from inspect import getsource as inspect_getsource
30
+ from mapFolding.filesystem import writeStringToHere
16
31
  from mapFolding.theSSOT import (
17
32
  getSourceAlgorithm,
33
+ raiseIfNoneGitHubIssueNumber3,
18
34
  theDataclassIdentifier,
19
35
  theDataclassInstance,
20
36
  theDataclassInstanceTaskDistribution,
@@ -32,9 +48,10 @@ from mapFolding.theSSOT import (
32
48
  theSourceParallelCallable,
33
49
  theSourceSequentialCallable,
34
50
  )
35
- from pathlib import PurePosixPath
51
+ from os import PathLike
52
+ from pathlib import Path, PurePath, PurePosixPath
36
53
  from types import ModuleType
37
- from typing import Any, Generic, cast, TypeAlias, TypeGuard, TypeVar
54
+ from typing import Any, cast, Generic, TypeAlias, TypeGuard, TypeVar
38
55
  from Z0Z_tools import updateExtendPolishDictionaryLists
39
56
  import ast
40
57
  import dataclasses
@@ -56,24 +73,15 @@ Namespace: uppercase, should only appear in camelCase and means "namespace", low
56
73
 
57
74
  # Would `LibCST` be better than `ast` in some cases? https://github.com/hunterhogan/mapFolding/issues/7
58
75
 
59
- nodeType = TypeVar('nodeType', bound=ast.AST)
60
- ast_Identifier: TypeAlias = str
61
76
  ast_expr_Slice: TypeAlias = ast.expr
62
- strDotStrCuzPyStoopid: TypeAlias = str
63
- strORlist_ast_type_paramORintORNone: TypeAlias = Any
64
- list_ast_type_paramORintORNone: TypeAlias = Any
65
- strORintORNone: TypeAlias = Any
77
+ ast_Identifier: TypeAlias = str
66
78
  astClassHasAttributeDOTname: TypeAlias = ast.FunctionDef | ast.ClassDef | ast.AsyncFunctionDef
67
79
  astMosDef = TypeVar('astMosDef', bound=astClassHasAttributeDOTname)
68
-
69
- @contextmanager
70
- def listAsNode(ImaList: list[ast.stmt]) -> Generator[ast.Module, None, None]:
71
- """Convert a list of AST nodes into a module for use in node walking tools."""
72
- ImaModule = ast.Module(ImaList)
73
- try:
74
- yield ImaModule
75
- finally:
76
- ImaList = ImaModule.body
80
+ list_ast_type_paramORintORNone: TypeAlias = Any
81
+ nodeType = TypeVar('nodeType', bound=ast.AST)
82
+ strDotStrCuzPyStoopid: TypeAlias = str
83
+ strORintORNone: TypeAlias = Any
84
+ strORlist_ast_type_paramORintORNone: TypeAlias = Any
77
85
 
78
86
  class NodeCollector(Generic[nodeType], ast.NodeVisitor):
79
87
  """A node visitor that collects data via one or more actions when a predicate is met."""
@@ -87,255 +95,189 @@ class NodeCollector(Generic[nodeType], ast.NodeVisitor):
87
95
  action(cast(nodeType, node))
88
96
  self.generic_visit(node)
89
97
 
90
- ImaViolationOfTheLiskovSubstitutionPrinciple: TypeAlias = ast.AST
91
98
  class NodeReplacer(Generic[nodeType], ast.NodeTransformer):
92
99
  """A node transformer that replaces or removes AST nodes based on a condition."""
93
100
  def __init__(self, findThis: Callable[[ast.AST], TypeGuard[nodeType] | bool], doThat: Callable[[nodeType], ast.AST | Sequence[ast.AST] | None]) -> None:
94
101
  self.findThis = findThis
95
102
  self.doThat = doThat
96
103
 
97
- def visit(self, node: ImaViolationOfTheLiskovSubstitutionPrinciple) -> ast.AST | Sequence[ast.AST] | None:
104
+ def visit(self, node: ast.AST) -> ast.AST | Sequence[ast.AST] | None:
98
105
  if self.findThis(node):
99
106
  return self.doThat(cast(nodeType, node))
100
107
  return super().visit(node)
101
108
 
102
109
  class ifThis:
103
110
  @staticmethod
104
- def ast_IdentifierIsIn(container: Container[ast_Identifier]) -> Callable[[ast_Identifier], TypeGuard[ast_Identifier]]:
105
- return lambda node: node in container # type: ignore
106
-
111
+ def ast_IdentifierIsIn(container: Container[ast_Identifier]) -> Callable[[ast_Identifier], TypeGuard[ast_Identifier] | bool]:
112
+ return lambda node: node in container
107
113
  @staticmethod
108
- def CallDoesNotCallItself(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Call]]:
114
+ def CallDoesNotCallItself(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Call] | bool]:
109
115
  """If `namespace` is not applicable to your case, then call with `namespace=""`."""
110
- return lambda node: ifThis.matchesMeButNotAnyDescendant(ifThis.CallReallyIs(namespace, identifier))(node) # type: ignore
111
-
116
+ return lambda node: ifThis.matchesMeButNotAnyDescendant(ifThis.CallReallyIs(namespace, identifier))(node)
112
117
  @staticmethod
113
- def CallDoesNotCallItselfAndNameDOTidIsIn(container: Container[ast_Identifier]) -> Callable[[ast.AST], TypeGuard[ast.Call]]:
114
- return lambda node: ifThis.isCall(node) and ifThis.isName(node.func) and ifThis.ast_IdentifierIsIn(container)(node.func.id) and ifThis.CallDoesNotCallItself("", node.func.id)(node) # type: ignore
115
-
118
+ def CallDoesNotCallItselfAndNameDOTidIsIn(container: Container[ast_Identifier]) -> Callable[[ast.AST], TypeGuard[ast.Call] | bool]:
119
+ return lambda node: ifThis.isCall(node) and ifThis.isName(node.func) and ifThis.ast_IdentifierIsIn(container)(node.func.id) and ifThis.CallDoesNotCallItself("", node.func.id)(node)
116
120
  @staticmethod
117
- def CallReallyIs(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Call]]:
118
- return ifThis.isAnyOf(ifThis.isCall_Identifier(identifier), ifThis.isCallNamespace_Identifier(namespace, identifier)) # type: ignore
119
-
121
+ def CallReallyIs(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Call] | bool]:
122
+ return ifThis.isAnyOf(ifThis.isCall_Identifier(identifier), ifThis.isCallNamespace_Identifier(namespace, identifier))
120
123
  @staticmethod
121
124
  def is_keyword(node: ast.AST) -> TypeGuard[ast.keyword]:
122
125
  return isinstance(node, ast.keyword)
123
-
124
126
  @staticmethod
125
127
  def is_keywordAndValueIsConstant(node: ast.AST) -> TypeGuard[ast.keyword]:
126
128
  return ifThis.is_keyword(node) and ifThis.isConstant(node.value)
127
-
128
129
  @staticmethod
129
- def is_keyword_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.keyword]]:
130
- return lambda node: ifThis.is_keyword(node) and node.arg == identifier # type: ignore
131
-
130
+ def is_keyword_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.keyword] | bool]:
131
+ def workhorse(node: ast.AST) -> TypeGuard[ast.keyword] | bool:
132
+ return ifThis.is_keyword(node) and node.arg == identifier
133
+ return workhorse
132
134
  @staticmethod
133
- def is_keyword_IdentifierEqualsConstantValue(identifier: ast_Identifier, ConstantValue: Any) -> Callable[[ast.AST], TypeGuard[ast.keyword]]:
134
- return lambda node: (ifThis.is_keyword_Identifier(identifier)(node) and ifThis.is_keywordAndValueIsConstant(node) and ifThis.isConstantEquals(ConstantValue)(node.value)) # type: ignore
135
-
135
+ def is_keyword_IdentifierEqualsConstantValue(identifier: ast_Identifier, ConstantValue: Any) -> Callable[[ast.AST], TypeGuard[ast.keyword] | bool]:
136
+ return lambda node: ifThis.is_keyword_Identifier(identifier)(node) and ifThis.is_keywordAndValueIsConstant(node) and ifThis.isConstantEquals(ConstantValue)(node.value)
136
137
  @staticmethod
137
138
  def isAllOf(*thesePredicates: Callable[[ast.AST], bool]) -> Callable[[ast.AST], bool]:
138
139
  return lambda node: all(predicate(node) for predicate in thesePredicates)
139
-
140
140
  @staticmethod
141
141
  def isAnnAssign(node: ast.AST) -> TypeGuard[ast.AnnAssign]:
142
142
  return isinstance(node, ast.AnnAssign)
143
-
144
143
  @staticmethod
145
144
  def isAnnAssignAndAnnotationIsName(node: ast.AST) -> TypeGuard[ast.AnnAssign]:
146
145
  return ifThis.isAnnAssign(node) and ifThis.isName(node.annotation)
147
-
148
146
  @staticmethod
149
147
  def isAnnAssignAndTargetIsName(node: ast.AST) -> TypeGuard[ast.AnnAssign]:
150
148
  return ifThis.isAnnAssign(node) and ifThis.isName(node.target)
151
-
152
149
  @staticmethod
153
- def isAnnAssignTo(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.AnnAssign]]:
154
- return lambda node: ifThis.isAnnAssign(node) and ifThis.NameReallyIs_Identifier(identifier)(node.target) # type: ignore
155
-
150
+ def isAnnAssignTo(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.AnnAssign] | bool]:
151
+ return lambda node: ifThis.isAnnAssign(node) and ifThis.NameReallyIs_Identifier(identifier)(node.target)
156
152
  @staticmethod
157
153
  def isAnyAssignmentTo(identifier: ast_Identifier) -> Callable[[ast.AST], bool]:
158
154
  return ifThis.isAnyOf(ifThis.isAssignOnlyTo(identifier), ifThis.isAnnAssignTo(identifier), ifThis.isAugAssignTo(identifier))
159
-
160
155
  @staticmethod
161
156
  def isAnyCompare(node: ast.AST) -> TypeGuard[ast.Compare] | TypeGuard[ast.BoolOp]:
162
157
  return ifThis.isCompare(node) or ifThis.isBoolOp(node)
163
-
164
158
  @staticmethod
165
159
  def isAnyOf(*thesePredicates: Callable[[ast.AST], bool]) -> Callable[[ast.AST], bool]:
166
160
  return lambda node: any(predicate(node) for predicate in thesePredicates)
167
-
168
161
  @staticmethod
169
162
  def isAssign(node: ast.AST) -> TypeGuard[ast.Assign]:
170
163
  return isinstance(node, ast.Assign)
171
-
172
164
  @staticmethod
173
- def isAssignAndValueIsCall_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Assign]]:
174
- return lambda node: ifThis.isAssign(node) and ifThis.isCall_Identifier(identifier)(node.value) # type: ignore
175
-
176
- # @staticmethod
177
- # def isAssignAndValueIsCallNamespace_Identifier(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Assign]]:
178
- # return lambda node: ifThis.isAssign(node) and ifThis.isCallNamespace_Identifier(namespace, identifier)(node.value)
179
-
165
+ def isAssignAndValueIsCall_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Assign] | bool]:
166
+ return lambda node: ifThis.isAssign(node) and ifThis.isCall_Identifier(identifier)(node.value)
180
167
  @staticmethod
181
- def isAssignAndValueIsCallNamespace_Identifier(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Assign]]:
168
+ def isAssignAndValueIsCallNamespace_Identifier(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Assign] | bool]:
182
169
  return ifThis.isAssignAndValueIs(ifThis.isCallNamespace_Identifier(namespace, identifier))
183
-
184
170
  @staticmethod
185
- def isAssignOnlyTo(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Assign]]:
186
- return lambda node: ifThis.isAssign(node) and ifThis.NameReallyIs_Identifier(identifier)(node.targets[0]) # type: ignore
187
-
171
+ def isAssignOnlyTo(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Assign] | bool]:
172
+ return lambda node: ifThis.isAssign(node) and ifThis.NameReallyIs_Identifier(identifier)(node.targets[0])
188
173
  @staticmethod
189
- def isAssignAndTargets0Is(targets0Predicate: Callable[[ast.AST], bool]) -> Callable[[ast.AST], TypeGuard[ast.Assign]]:
174
+ def isAssignAndTargets0Is(targets0Predicate: Callable[[ast.AST], bool]) -> Callable[[ast.AST], TypeGuard[ast.Assign] | bool]:
190
175
  """node is Assign and node.targets[0] matches `targets0Predicate`."""
191
- return lambda node: ifThis.isAssign(node) and targets0Predicate(node.targets[0]) # type: ignore
192
-
176
+ return lambda node: ifThis.isAssign(node) and targets0Predicate(node.targets[0])
193
177
  @staticmethod
194
- def isAssignAndValueIs(valuePredicate: Callable[[ast.AST], bool]) -> Callable[[ast.AST], TypeGuard[ast.Assign]]:
195
- """node is Assign and node.value matches `valuePredicate`.
196
-
178
+ def isAssignAndValueIs(valuePredicate: Callable[[ast.AST], bool]) -> Callable[[ast.AST], TypeGuard[ast.Assign] | bool]:
179
+ """node is ast.Assign and node.value matches `valuePredicate`.
197
180
  Parameters:
198
181
  valuePredicate: Function that evaluates the value of the assignment
199
-
200
182
  Returns:
201
- A predicate that matches assignments with values meeting the criteria
183
+ predicate: matches assignments with values meeting the criteria
202
184
  """
203
- return lambda node: ifThis.isAssign(node) and valuePredicate(node.value) # type: ignore
204
-
185
+ return lambda node: ifThis.isAssign(node) and valuePredicate(node.value)
205
186
  @staticmethod
206
187
  def isAttribute(node: ast.AST) -> TypeGuard[ast.Attribute]:
207
188
  return isinstance(node, ast.Attribute)
208
-
209
189
  @staticmethod
210
190
  def isAugAssign(node: ast.AST) -> TypeGuard[ast.AugAssign]:
211
191
  return isinstance(node, ast.AugAssign)
212
-
213
192
  @staticmethod
214
- def isAugAssignTo(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.AugAssign]]:
215
- return lambda node: ifThis.isAugAssign(node) and ifThis.NameReallyIs_Identifier(identifier)(node.target) # type: ignore
216
-
193
+ def isAugAssignTo(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.AugAssign] | bool]:
194
+ return lambda node: ifThis.isAugAssign(node) and ifThis.NameReallyIs_Identifier(identifier)(node.target)
217
195
  @staticmethod
218
196
  def isBoolOp(node: ast.AST) -> TypeGuard[ast.BoolOp]:
219
197
  return isinstance(node, ast.BoolOp)
220
-
221
198
  @staticmethod
222
199
  def isCall(node: ast.AST) -> TypeGuard[ast.Call]:
223
200
  return isinstance(node, ast.Call)
224
-
225
201
  @staticmethod
226
- def isCall_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Call]]:
227
- return lambda node: ifThis.isCall(node) and ifThis.isName_Identifier(identifier)(node.func) # type: ignore
228
-
202
+ def isCall_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Call] | bool]:
203
+ return lambda node: ifThis.isCall(node) and ifThis.isName_Identifier(identifier)(node.func)
229
204
  @staticmethod
230
- def isCallNamespace_Identifier(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Call]]:
231
- return lambda node: ifThis.isCall(node) and ifThis.is_nameDOTnameNamespace_Identifier(namespace, identifier)(node.func) # type: ignore
232
-
205
+ def isCallNamespace_Identifier(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Call] | bool]:
206
+ return lambda node: ifThis.isCall(node) and ifThis.is_nameDOTnameNamespace_Identifier(namespace, identifier)(node.func)
233
207
  @staticmethod
234
208
  def isCallToName(node: ast.AST) -> TypeGuard[ast.Call]:
235
209
  return ifThis.isCall(node) and ifThis.isName(node.func)
236
-
237
210
  @staticmethod
238
211
  def isClassDef(node: ast.AST) -> TypeGuard[ast.ClassDef]:
239
212
  return isinstance(node, ast.ClassDef)
240
-
241
213
  @staticmethod
242
- def isClassDef_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.ClassDef]]:
243
- return lambda node: ifThis.isClassDef(node) and node.name == identifier # type: ignore
244
-
214
+ def isClassDef_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.ClassDef] | bool]:
215
+ return lambda node: ifThis.isClassDef(node) and node.name == identifier
245
216
  @staticmethod
246
217
  def isCompare(node: ast.AST) -> TypeGuard[ast.Compare]:
247
218
  return isinstance(node, ast.Compare)
248
-
249
219
  @staticmethod
250
220
  def isConstant(node: ast.AST) -> TypeGuard[ast.Constant]:
251
221
  return isinstance(node, ast.Constant)
252
-
253
222
  @staticmethod
254
- def isConstantEquals(value: Any) -> Callable[[ast.AST], TypeGuard[ast.Constant]]:
255
- return lambda node: ifThis.isConstant(node) and node.value == value # type: ignore
256
-
223
+ def isConstantEquals(value: Any) -> Callable[[ast.AST], TypeGuard[ast.Constant] | bool]:
224
+ return lambda node: ifThis.isConstant(node) and node.value == value
257
225
  @staticmethod
258
226
  def isExpr(node: ast.AST) -> TypeGuard[ast.Expr]:
259
227
  return isinstance(node, ast.Expr)
260
-
261
228
  @staticmethod
262
229
  def isFunctionDef(node: ast.AST) -> TypeGuard[ast.FunctionDef]:
263
230
  return isinstance(node, ast.FunctionDef)
264
-
265
231
  @staticmethod
266
- def isFunctionDef_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.FunctionDef]]:
267
- return lambda node: ifThis.isFunctionDef(node) and node.name == identifier # type: ignore
268
-
232
+ def isFunctionDef_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.FunctionDef] | bool]:
233
+ return lambda node: ifThis.isFunctionDef(node) and node.name == identifier
269
234
  @staticmethod
270
235
  def isImport(node: ast.AST) -> TypeGuard[ast.Import]:
271
236
  return isinstance(node, ast.Import)
272
-
273
- # A "name" is roughly ast._Identifier
274
-
275
- # A Name can be a "name"
276
- # ast.Name()
277
-
278
- # These classes can be a "name": I will call the group 'ClassQuizas'
279
- # The technical mechanism is `type[ast.ClassQuizas.value] == ast.Name`
280
- # or, and this is the annoying part,
281
- # `type[ast.ClassQuizas.value] == ast.ClassQuizas and type[ast.ClassQuizas.value.value] == ast.ClassQuizas and ... == ast.Name`
282
- # ast.Attribute()
283
- # ast.Subscript()
284
- # ast.Starred()
285
-
286
237
  @staticmethod
287
238
  def isName(node: ast.AST) -> TypeGuard[ast.Name]:
239
+ """TODO
240
+ ast.Name()
241
+ ast.Attribute()
242
+ ast.Subscript()
243
+ ast.Starred()
244
+ """
288
245
  return isinstance(node, ast.Name)
289
-
290
246
  @staticmethod
291
- def isName_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Name]]:
292
- return lambda node: ifThis.isName(node) and node.id == identifier # type: ignore
293
-
247
+ def isName_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Name] | bool]:
248
+ return lambda node: ifThis.isName(node) and node.id == identifier
294
249
  @staticmethod
295
250
  def is_nameDOTname(node: ast.AST) -> TypeGuard[ast.Attribute]:
296
251
  return ifThis.isAttribute(node) and ifThis.isName(node.value)
297
-
298
252
  @staticmethod
299
- def is_nameDOTnameNamespace(namespace: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Attribute]]:
300
- return lambda node: (ifThis.is_nameDOTname(node) and ifThis.isName_Identifier(namespace)(node.value)) # type: ignore
301
-
253
+ def is_nameDOTnameNamespace(namespace: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Attribute] | bool]:
254
+ return lambda node: ifThis.is_nameDOTname(node) and ifThis.isName_Identifier(namespace)(node.value)
302
255
  @staticmethod
303
- def is_nameDOTnameNamespace_Identifier(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Attribute]]:
304
- return lambda node: (ifThis.is_nameDOTnameNamespace(namespace)(node) and node.attr == identifier) # type: ignore
305
-
306
- @staticmethod
307
- def isAnyNestedName(node: ast.AST):
308
- return ifThis.isAnyOf()
309
-
256
+ def is_nameDOTnameNamespace_Identifier(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Attribute] | bool]:
257
+ return lambda node: ifThis.is_nameDOTname(node) and ifThis.isName_Identifier(namespace)(node.value) and node.attr == identifier
310
258
  @staticmethod
311
259
  def NameReallyIs_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], bool]:
312
260
  # The following logic is incomplete.
313
261
  return ifThis.isAnyOf(ifThis.isName_Identifier(identifier), ifThis.isSubscriptIsName_Identifier(identifier))
314
-
315
262
  @staticmethod
316
263
  def isReturn(node: ast.AST) -> TypeGuard[ast.Return]:
317
264
  return isinstance(node, ast.Return)
318
-
319
265
  @staticmethod
320
266
  def isReturnAnyCompare(node: ast.AST) -> TypeGuard[ast.Return]:
321
267
  return ifThis.isReturn(node) and node.value is not None and ifThis.isAnyCompare(node.value)
322
-
323
268
  @staticmethod
324
269
  def isReturnUnaryOp(node: ast.AST) -> TypeGuard[ast.Return]:
325
270
  return ifThis.isReturn(node) and node.value is not None and ifThis.isUnaryOp(node.value)
326
-
327
271
  @staticmethod
328
272
  def isSubscript(node: ast.AST) -> TypeGuard[ast.Subscript]:
329
273
  return isinstance(node, ast.Subscript)
330
-
331
274
  @staticmethod
332
275
  def isSubscript_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Subscript]]:
333
276
  """node is `ast.Subscript` and the top-level `ast.Name` is `identifier`
334
277
  Parameters:
335
278
  identifier: The identifier to look for in the value chain
336
-
337
279
  Returns:
338
- A predicate function that checks if a node matches the criteria
280
+ predicate: function that checks if a node matches the criteria
339
281
  """
340
282
  def predicate(node: ast.AST) -> TypeGuard[ast.Subscript]:
341
283
  if not ifThis.isSubscript(node):
@@ -349,26 +291,21 @@ class ifThis:
349
291
  return False
350
292
  return checkNodeDOTvalue(node.value)
351
293
  return predicate
352
-
353
294
  @staticmethod
354
- def isSubscriptIsName_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Subscript]]:
355
- return lambda node: ifThis.isSubscript(node) and ifThis.isName_Identifier(identifier)(node.value) # type: ignore
356
-
295
+ def isSubscriptIsName_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Subscript] | bool]:
296
+ return lambda node: ifThis.isSubscript(node) and ifThis.isName_Identifier(identifier)(node.value)
357
297
  @staticmethod
358
- def isSubscript_Identifier_Identifier(identifier: ast_Identifier, sliceIdentifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Subscript]]:
359
- return lambda node: ifThis.isSubscript(node) and ifThis.isName_Identifier(identifier)(node.value) and ifThis.isName_Identifier(sliceIdentifier)(node.slice) # type: ignore
360
-
298
+ def isSubscript_Identifier_Identifier(identifier: ast_Identifier, sliceIdentifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Subscript] | bool]:
299
+ return lambda node: ifThis.isSubscript(node) and ifThis.isName_Identifier(identifier)(node.value) and ifThis.isName_Identifier(sliceIdentifier)(node.slice)
361
300
  @staticmethod
362
301
  def isUnaryOp(node: ast.AST) -> TypeGuard[ast.UnaryOp]:
363
302
  return isinstance(node, ast.UnaryOp)
364
-
365
- # Does this work?
303
+ # TODO Does this work?
366
304
  @staticmethod
367
305
  def matchesAtLeast1Descendant(predicate: Callable[[ast.AST], bool]) -> Callable[[ast.AST], bool]:
368
306
  """Create a predicate that returns True if any descendant of the node matches the given predicate."""
369
307
  return lambda node: not ifThis.matchesNoDescendant(predicate)(node)
370
-
371
- # Does this work?
308
+ # TODO Does this work?
372
309
  @staticmethod
373
310
  def matchesMeAndMyDescendantsExactlyNTimes(predicate: Callable[[ast.AST], bool], nTimes: int) -> Callable[[ast.AST], bool]:
374
311
  """Create a predicate that returns True if exactly 'count' nodes in the tree match the predicate."""
@@ -376,12 +313,10 @@ class ifThis:
376
313
  matches = sum(1 for descendant in ast.walk(node) if predicate(descendant))
377
314
  return matches == nTimes
378
315
  return countMatchingNodes
379
-
380
316
  @staticmethod
381
317
  def matchesMeButNotAnyDescendant(predicate: Callable[[ast.AST], bool]) -> Callable[[ast.AST], bool]:
382
318
  """Create a predicate that returns True if the node matches but none of its descendants match the predicate."""
383
319
  return lambda node: predicate(node) and ifThis.matchesNoDescendant(predicate)(node)
384
-
385
320
  @staticmethod
386
321
  def matchesNoDescendant(predicate: Callable[[ast.AST], bool]) -> Callable[[ast.AST], bool]:
387
322
  """Create a predicate that returns True if no descendant of the node matches the given predicate."""
@@ -391,13 +326,11 @@ class ifThis:
391
326
  return False
392
327
  return True
393
328
  return checkNoMatchingDescendant
394
-
395
329
  @staticmethod
396
- def OnlyReturnAnyCompare(astFunctionDef: ast.AST) -> TypeGuard[ast.FunctionDef]:
330
+ def onlyReturnAnyCompare(astFunctionDef: ast.AST) -> TypeGuard[ast.FunctionDef]:
397
331
  return ifThis.isFunctionDef(astFunctionDef) and len(astFunctionDef.body) == 1 and ifThis.isReturnAnyCompare(astFunctionDef.body[0])
398
-
399
332
  @staticmethod
400
- def OnlyReturnUnaryOp(astFunctionDef: ast.AST) -> TypeGuard[ast.FunctionDef]:
333
+ def onlyReturnUnaryOp(astFunctionDef: ast.AST) -> TypeGuard[ast.FunctionDef]:
401
334
  return ifThis.isFunctionDef(astFunctionDef) and len(astFunctionDef.body) == 1 and ifThis.isReturnUnaryOp(astFunctionDef.body[0])
402
335
 
403
336
  class Make:
@@ -405,29 +338,23 @@ class Make:
405
338
  def ast_arg(identifier: ast_Identifier, annotation: ast.expr | None = None, **keywordArguments: strORintORNone) -> ast.arg:
406
339
  """keywordArguments: type_comment:str|None, lineno:int, col_offset:int, end_lineno:int|None, end_col_offset:int|None"""
407
340
  return ast.arg(identifier, annotation, **keywordArguments)
408
-
409
341
  @staticmethod
410
342
  def ast_keyword(keywordArgument: ast_Identifier, value: ast.expr, **keywordArguments: int) -> ast.keyword:
411
343
  return ast.keyword(arg=keywordArgument, value=value, **keywordArguments)
412
-
413
344
  @staticmethod
414
345
  def astAlias(name: ast_Identifier, asname: ast_Identifier | None = None) -> ast.alias:
415
346
  return ast.alias(name, asname)
416
-
417
347
  @staticmethod
418
348
  def astAnnAssign(target: ast.Name | ast.Attribute | ast.Subscript, annotation: ast.expr, value: ast.expr | None = None, **keywordArguments: int) -> ast.AnnAssign:
419
349
  """`simple: int`: uses a clever int-from-boolean to assign the correct value to the `simple` attribute. So, don't add it as a parameter."""
420
350
  return ast.AnnAssign(target, annotation, value, simple=int(isinstance(target, ast.Name)), **keywordArguments)
421
-
422
351
  @staticmethod
423
352
  def astAssign(listTargets: Any, value: ast.expr, **keywordArguments: strORintORNone) -> ast.Assign:
424
353
  """keywordArguments: type_comment:str|None, lineno:int, col_offset:int, end_lineno:int|None, end_col_offset:int|None"""
425
354
  return ast.Assign(targets=listTargets, value=value, **keywordArguments)
426
-
427
355
  @staticmethod
428
356
  def astArgumentsSpecification(posonlyargs: list[ast.arg]=[], args: list[ast.arg]=[], vararg: ast.arg|None=None, kwonlyargs: list[ast.arg]=[], kw_defaults: list[ast.expr|None]=[None], kwarg: ast.arg|None=None, defaults: list[ast.expr]=[]) -> ast.arguments:
429
357
  return ast.arguments(posonlyargs, args, vararg, kwonlyargs, kw_defaults, kwarg, defaults)
430
-
431
358
  @staticmethod
432
359
  def astAttribute(value: ast.expr, attribute: ast_Identifier, context: ast.expr_context = ast.Load(), **keywordArguments: int) -> ast.Attribute:
433
360
  """
@@ -436,47 +363,37 @@ class Make:
436
363
  attribute: the `str` after the dot
437
364
  context (ast.Load()): Load/Store/Del"""
438
365
  return ast.Attribute(value, attribute, context, **keywordArguments)
439
-
440
366
  @staticmethod
441
367
  def astCall(caller: ast.Name | ast.Attribute, listArguments: Sequence[ast.expr] | None = None, list_astKeywords: Sequence[ast.keyword] | None = None) -> ast.Call:
442
368
  return ast.Call(func=caller, args=list(listArguments) if listArguments else [], keywords=list(list_astKeywords) if list_astKeywords else [])
443
-
444
369
  @staticmethod
445
370
  def astClassDef(name: ast_Identifier, listBases: list[ast.expr]=[], list_keyword: list[ast.keyword]=[], body: list[ast.stmt]=[], decorator_list: list[ast.expr]=[], **keywordArguments: list_ast_type_paramORintORNone) -> ast.ClassDef:
446
371
  """keywordArguments: type_params:list[ast.type_param], lineno:int, col_offset:int, end_lineno:int|None, end_col_offset:int|None"""
447
372
  return ast.ClassDef(name=name, bases=listBases, keywords=list_keyword, body=body, decorator_list=decorator_list, **keywordArguments)
448
-
449
373
  @staticmethod
450
374
  def astConstant(value: Any, **keywordArguments: strORintORNone) -> ast.Constant:
451
375
  """value: str|int|float|bool|None|bytes|bytearray|memoryview|complex|list|tuple|dict|set, or any other type that can be represented as a constant in Python.
452
376
  keywordArguments: kind:str, lineno:int, col_offset:int, end_lineno:int|None, end_col_offset:int|None"""
453
377
  return ast.Constant(value, **keywordArguments)
454
-
455
378
  @staticmethod
456
379
  def astFunctionDef(name: ast_Identifier, argumentsSpecification: ast.arguments=ast.arguments(), body: list[ast.stmt]=[], decorator_list: list[ast.expr]=[], returns: ast.expr|None=None, **keywordArguments: strORlist_ast_type_paramORintORNone) -> ast.FunctionDef:
457
380
  """keywordArguments: type_comment:str|None, type_params:list[ast.type_param], lineno:int, col_offset:int, end_lineno:int|None, end_col_offset:int|None"""
458
381
  return ast.FunctionDef(name=name, args=argumentsSpecification, body=body, decorator_list=decorator_list, returns=returns, **keywordArguments)
459
-
460
382
  @staticmethod
461
383
  def astImport(moduleName: ast_Identifier, asname: ast_Identifier | None = None, **keywordArguments: int) -> ast.Import:
462
384
  return ast.Import(names=[Make.astAlias(moduleName, asname)], **keywordArguments)
463
-
464
385
  @staticmethod
465
386
  def astImportFrom(moduleName: ast_Identifier, list_astAlias: list[ast.alias], **keywordArguments: int) -> ast.ImportFrom:
466
387
  return ast.ImportFrom(module=moduleName, names=list_astAlias, level=0, **keywordArguments)
467
-
468
388
  @staticmethod
469
389
  def astModule(body: list[ast.stmt], type_ignores: list[ast.TypeIgnore] = []) -> ast.Module:
470
390
  return ast.Module(body, type_ignores)
471
-
472
391
  @staticmethod
473
392
  def astName(identifier: ast_Identifier, context: ast.expr_context = ast.Load(), **keywordArguments: int) -> ast.Name:
474
393
  return ast.Name(identifier, context, **keywordArguments)
475
-
476
394
  @staticmethod
477
395
  def itDOTname(nameChain: ast.Name | ast.Attribute, dotName: str) -> ast.Attribute:
478
396
  return ast.Attribute(value=nameChain, attr=dotName, ctx=ast.Load())
479
-
480
397
  @staticmethod
481
398
  # TODO rewrite with all parameters
482
399
  def nameDOTname(identifier: ast_Identifier, *dotName: str) -> ast.Name | ast.Attribute:
@@ -486,20 +403,15 @@ class Make:
486
403
  for suffix in dotName:
487
404
  nameDOTname = Make.itDOTname(nameDOTname, suffix)
488
405
  return nameDOTname
489
-
490
406
  @staticmethod
491
407
  def astReturn(value: ast.expr | None = None, **keywordArguments: int) -> ast.Return:
492
408
  return ast.Return(value, **keywordArguments)
493
-
494
409
  @staticmethod
495
410
  def astSubscript(value: ast.expr, slice: ast_expr_Slice, context: ast.expr_context = ast.Load(), **keywordArguments: int) -> ast.Subscript:
496
411
  return ast.Subscript(value, slice, ctx=context, **keywordArguments)
497
-
498
412
  @staticmethod
499
413
  def astTuple(elements: Sequence[ast.expr], context: ast.expr_context = ast.Load(), **keywordArguments: int) -> ast.Tuple:
500
- # def astTuple(elements: Sequence[ast.expr], context: ast.expr_context | None = None, **keywordArguments: int) -> ast.Tuple:
501
414
  """context: Load/Store/Del"""
502
- # context = context or ast.Load()
503
415
  return ast.Tuple(elts=list(elements), ctx=context, **keywordArguments)
504
416
 
505
417
  class LedgerOfImports:
@@ -513,8 +425,7 @@ class LedgerOfImports:
513
425
  self.walkThis(startWith)
514
426
 
515
427
  def addAst(self, astImport_: ast.Import | ast.ImportFrom) -> None:
516
- if not isinstance(astImport_, (ast.Import, ast.ImportFrom)):
517
- raise ValueError(f"Expected ast.Import or ast.ImportFrom, got {type(astImport_)}")
428
+ assert isinstance(astImport_, (ast.Import, ast.ImportFrom)), f"Expected ast.Import or ast.ImportFrom, got {type(astImport_)}"
518
429
  if isinstance(astImport_, ast.Import):
519
430
  for alias in astImport_.names:
520
431
  self.listImport.append(alias.name)
@@ -594,8 +505,6 @@ class Then:
594
505
  def Z0Z_appendAnnAssignOf_nameDOTnameTo(identifier: ast_Identifier, list_nameDOTname: list[ast.AnnAssign]) -> Callable[[ast.AnnAssign], None]:
595
506
  return lambda node: list_nameDOTname.append(Make.astAnnAssign(node.target, node.annotation, Make.nameDOTname(identifier, node.target.id))) # type: ignore
596
507
 
597
- # TODO When resolving the ledger of imports, remove self-referential imports
598
-
599
508
  @dataclasses.dataclass
600
509
  class IngredientsFunction:
601
510
  """Everything necessary to integrate a function into a module should be here."""
@@ -606,11 +515,6 @@ class IngredientsFunction:
606
515
  class IngredientsModule:
607
516
  """Everything necessary to create one _logical_ `ast.Module` should be here.
608
517
  Extrinsic qualities should _probably_ be handled externally."""
609
- # If an `ast.Module` had a logical name that would be reasonable, but Python is firmly opposed
610
- # to a reasonable namespace, therefore, Hunter, you were silly to add a `name` field to this
611
- # dataclass for building an `ast.Module`.
612
- # name: ast_Identifier
613
- # Hey, genius, note that this is dataclasses.InitVar
614
518
  ingredientsFunction: dataclasses.InitVar[Sequence[IngredientsFunction] | IngredientsFunction | None] = None
615
519
 
616
520
  # init var with an existing module? method to deconstruct an existing module?
@@ -676,7 +580,7 @@ class RecipeSynthesizeFlow:
676
580
 
677
581
  # ========================================
678
582
  # Filesystem
679
- pathPackage: PurePosixPath = PurePosixPath(thePathPackage)
583
+ pathPackage: PurePosixPath | None = PurePosixPath(thePathPackage)
680
584
  fileExtension: str = theFileExtension
681
585
 
682
586
  # ========================================
@@ -686,7 +590,7 @@ class RecipeSynthesizeFlow:
686
590
  formatStrModuleForCallableSynthetic: str = theFormatStrModuleForCallableSynthetic
687
591
 
688
592
  # Package
689
- packageName: ast_Identifier = thePackageName
593
+ packageName: ast_Identifier | None = thePackageName
690
594
 
691
595
  # Module
692
596
  # Figure out dynamic flow control to synthesized modules https://github.com/hunterhogan/mapFolding/issues/4
@@ -708,6 +612,26 @@ class RecipeSynthesizeFlow:
708
612
  # Variable
709
613
  dataclassInstance: str = sourceDataclassInstance
710
614
 
615
+ def _makePathFilename(self, filenameStem: str,
616
+ pathRoot: PurePosixPath | None = None,
617
+ logicalPathINFIX: strDotStrCuzPyStoopid | None = None,
618
+ fileExtension: str | None = None,
619
+ ) -> PurePosixPath:
620
+ """filenameStem: (hint: the name of the logical module)"""
621
+ if pathRoot is None:
622
+ pathRoot = self.pathPackage or PurePosixPath(Path.cwd())
623
+ if logicalPathINFIX:
624
+ whyIsThisStillAThing: list[str] = logicalPathINFIX.split('.')
625
+ pathRoot = pathRoot.joinpath(*whyIsThisStillAThing)
626
+ if fileExtension is None:
627
+ fileExtension = self.fileExtension
628
+ filename: str = filenameStem + fileExtension
629
+ return pathRoot.joinpath(filename)
630
+
631
+ @property
632
+ def pathFilenameDispatcher(self) -> PurePosixPath:
633
+ return self._makePathFilename(filenameStem=self.moduleDispatcher, logicalPathINFIX=self.Z0Z_flowLogicalPathRoot)
634
+
711
635
  def extractClassDef(identifier: ast_Identifier, module: ast.Module) -> ast.ClassDef | None:
712
636
  sherpa: list[ast.ClassDef] = []
713
637
  extractor = NodeCollector(ifThis.isClassDef_Identifier(identifier), [Then.appendTo(sherpa)])
@@ -732,9 +656,9 @@ def makeDictionaryReplacementStatements(module: ast.Module) -> dict[ast_Identifi
732
656
  dictionaryFunctionDef: dict[ast_Identifier, ast.FunctionDef] = makeDictionaryFunctionDef(module)
733
657
  dictionaryReplacementStatements: dict[ast_Identifier, ast.stmt | list[ast.stmt]] = {}
734
658
  for name, astFunctionDef in dictionaryFunctionDef.items():
735
- if ifThis.OnlyReturnAnyCompare(astFunctionDef):
659
+ if ifThis.onlyReturnAnyCompare(astFunctionDef):
736
660
  dictionaryReplacementStatements[name] = astFunctionDef.body[0].value # type: ignore
737
- elif ifThis.OnlyReturnUnaryOp(astFunctionDef):
661
+ elif ifThis.onlyReturnUnaryOp(astFunctionDef):
738
662
  dictionaryReplacementStatements[name] = astFunctionDef.body[0].value # type: ignore
739
663
  else:
740
664
  dictionaryReplacementStatements[name] = astFunctionDef.body[0:-1]
@@ -761,3 +685,96 @@ def Z0Z_executeActionUnlessDescendantMatches(exclusionPredicate: Callable[[ast.A
761
685
  if not Z0Z_descendantContainsMatchingNode(node, exclusionPredicate):
762
686
  actionFunction(node)
763
687
  return wrappedAction
688
+
689
+ def inlineThisFunctionWithTheseValues(astFunctionDef: ast.FunctionDef, dictionaryReplacementStatements: dict[str, ast.stmt | list[ast.stmt]]) -> ast.FunctionDef:
690
+ class FunctionInliner(ast.NodeTransformer):
691
+ def __init__(self, dictionaryReplacementStatements: dict[str, ast.stmt | list[ast.stmt]]) -> None:
692
+ self.dictionaryReplacementStatements = dictionaryReplacementStatements
693
+
694
+ def generic_visit(self, node: ast.AST) -> ast.AST:
695
+ """Visit all nodes and replace them if necessary."""
696
+ return super().generic_visit(node)
697
+
698
+ def visit_Expr(self, node: ast.Expr) -> ast.AST | list[ast.stmt]:
699
+ if ifThis.CallDoesNotCallItselfAndNameDOTidIsIn(self.dictionaryReplacementStatements)(node.value):
700
+ return self.dictionaryReplacementStatements[node.value.func.id] # type: ignore[attr-defined]
701
+ return node
702
+
703
+ def visit_Assign(self, node: ast.Assign) -> ast.AST | list[ast.stmt]:
704
+ if ifThis.CallDoesNotCallItselfAndNameDOTidIsIn(self.dictionaryReplacementStatements)(node.value):
705
+ return self.dictionaryReplacementStatements[node.value.func.id] # type: ignore[attr-defined]
706
+ return node
707
+
708
+ def visit_Call(self, node: ast.Call) -> ast.AST | list[ast.stmt]:
709
+ if ifThis.CallDoesNotCallItselfAndNameDOTidIsIn(self.dictionaryReplacementStatements)(node):
710
+ replacement = self.dictionaryReplacementStatements[node.func.id] # type: ignore[attr-defined]
711
+ if not isinstance(replacement, list):
712
+ return replacement
713
+ return node
714
+
715
+ keepGoing = True
716
+ ImaInlineFunction = deepcopy(astFunctionDef)
717
+ while keepGoing:
718
+ ImaInlineFunction = deepcopy(astFunctionDef)
719
+ FunctionInliner(deepcopy(dictionaryReplacementStatements)).visit(ImaInlineFunction)
720
+ if ast.unparse(ImaInlineFunction) == ast.unparse(astFunctionDef):
721
+ keepGoing = False
722
+ else:
723
+ astFunctionDef = deepcopy(ImaInlineFunction)
724
+ ast.fix_missing_locations(astFunctionDef)
725
+ return ImaInlineFunction
726
+
727
+ def Z0Z_replaceMatchingASTnodes(astTree: ast.AST, mappingFindReplaceNodes: dict[ast.AST, ast.AST]) -> ast.AST:
728
+ class TargetedNodeReplacer(ast.NodeTransformer):
729
+ def __init__(self, mappingFindReplaceNodes: dict[ast.AST, ast.AST]) -> None:
730
+ self.mappingFindReplaceNodes = mappingFindReplaceNodes
731
+
732
+ def visit(self, node: ast.AST) -> ast.AST:
733
+ for nodeFind, nodeReplace in self.mappingFindReplaceNodes.items():
734
+ if self.nodesMatchStructurally(node, nodeFind):
735
+ return nodeReplace
736
+ return self.generic_visit(node)
737
+
738
+ def nodesMatchStructurally(self, nodeSubject: ast.AST | list[Any] | Any, nodePattern: ast.AST | list[Any] | Any) -> bool:
739
+ if nodeSubject is None or nodePattern is None:
740
+ return nodeSubject is None and nodePattern is None
741
+
742
+ if type(nodeSubject) != type(nodePattern):
743
+ return False
744
+
745
+ if isinstance(nodeSubject, ast.AST):
746
+ for field, fieldValueSubject in ast.iter_fields(nodeSubject):
747
+ if field in ('lineno', 'col_offset', 'end_lineno', 'end_col_offset', 'ctx'):
748
+ continue
749
+ attrPattern = getattr(nodePattern, field, None)
750
+ if not self.nodesMatchStructurally(fieldValueSubject, attrPattern):
751
+ return False
752
+ return True
753
+
754
+ if isinstance(nodeSubject, list) and isinstance(nodePattern, list):
755
+ nodeSubjectList: list[Any] = nodeSubject
756
+ nodePatternList: list[Any] = nodePattern
757
+ return len(nodeSubjectList) == len(nodePatternList) and all(
758
+ self.nodesMatchStructurally(elementSubject, elementPattern)
759
+ for elementSubject, elementPattern in zip(nodeSubjectList, nodePatternList)
760
+ )
761
+
762
+ return nodeSubject == nodePattern
763
+
764
+ astTreeCurrent, astTreePrevious = None, astTree
765
+ while astTreeCurrent is None or ast.unparse(astTreeCurrent) != ast.unparse(astTreePrevious):
766
+ astTreePrevious = astTreeCurrent if astTreeCurrent else astTree
767
+ astTreeCurrent = TargetedNodeReplacer(mappingFindReplaceNodes).visit(astTreePrevious)
768
+
769
+ return astTreeCurrent
770
+
771
+ def write_astModule(ingredients: IngredientsModule, pathFilename: str | PathLike[Any] | PurePath, packageName: ast_Identifier | None = None) -> None:
772
+ astModule = ingredients.export()
773
+ ast.fix_missing_locations(astModule)
774
+ pythonSource: str = ast.unparse(astModule)
775
+ if not pythonSource: raise raiseIfNoneGitHubIssueNumber3
776
+ autoflake_additional_imports: list[str] = ingredients.imports.exportListModuleNames()
777
+ if packageName:
778
+ autoflake_additional_imports.append(packageName)
779
+ pythonSource = autoflake_fix_code(pythonSource, autoflake_additional_imports, expand_star_imports=False, remove_all_unused_imports=False, remove_duplicate_keys = False, remove_unused_variables = False)
780
+ writeStringToHere(pythonSource, pathFilename)