-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathcontext.py
More file actions
164 lines (140 loc) · 6.09 KB
/
Copy pathcontext.py
File metadata and controls
164 lines (140 loc) · 6.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
import logging
from pathlib import Path
import itertools
from textwrap import indent
from typing import List, Iterator
from codemodder.change import ChangeSet
from codemodder.dependency import (
Dependency,
build_dependency_notification,
build_failed_dependency_notification,
)
from codemodder.executor import CodemodExecutorWrapper
from codemodder.file_context import FileContext
from codemodder.logging import logger, log_list
from codemodder.project_analysis.file_parsers.package_store import PackageStore
from codemodder.registry import CodemodRegistry
from codemodder.project_analysis.python_repo_manager import PythonRepoManager
from codemodder.utils.timer import Timer
class CodemodExecutionContext: # pylint: disable=too-many-instance-attributes
_results_by_codemod: dict[str, list[ChangeSet]] = {}
_failures_by_codemod: dict[str, list[Path]] = {}
_dependency_update_by_codemod: dict[str, PackageStore | None] = {}
dependencies: dict[str, set[Dependency]] = {}
directory: Path
dry_run: bool = False
verbose: bool = False
registry: CodemodRegistry
repo_manager: PythonRepoManager
timer: Timer
def __init__(
self,
directory: Path,
dry_run: bool,
verbose: bool,
registry: CodemodRegistry,
repo_manager: PythonRepoManager,
): # pylint: disable=too-many-arguments
self.directory = directory
self.dry_run = dry_run
self.verbose = verbose
self._results_by_codemod = {}
self._failures_by_codemod = {}
self.dependencies = {}
self.registry = registry
self.repo_manager = repo_manager
self.timer = Timer()
def add_results(self, codemod_name: str, change_sets: List[ChangeSet]):
self._results_by_codemod.setdefault(codemod_name, []).extend(change_sets)
def add_failures(self, codemod_name: str, failed_files: List[Path]):
self._failures_by_codemod.setdefault(codemod_name, []).extend(failed_files)
def add_dependencies(self, codemod_id: str, dependencies: set[Dependency]):
self.dependencies.setdefault(codemod_id, set()).update(dependencies)
def get_results(self, codemod_name: str):
return self._results_by_codemod.get(codemod_name, [])
def get_changed_files(self):
return [
change_set.path
for changes in self._results_by_codemod.values()
for change_set in changes
]
def get_failures(self, codemod_name: str):
return self._failures_by_codemod.get(codemod_name, [])
def get_failed_files(self):
return list(
itertools.chain.from_iterable(
failures for failures in self._failures_by_codemod.values()
)
)
def process_dependencies(
self, codemod_id: str
) -> dict[Dependency, PackageStore | None]:
"""Write the dependencies a codemod added to the appropriate dependency
file in the project. Returns a dict listing the locations the dependencies were added.
"""
dependencies = self.dependencies.get(codemod_id)
if not dependencies:
return {}
# populate everything with None and then change the ones added
record: dict[Dependency, PackageStore | None] = {}
for dep in dependencies:
record[dep] = None
store_list = self.repo_manager.package_stores
if not store_list:
logger.info(
"unable to write dependencies for %s: no dependency file found",
codemod_id,
)
self._dependency_update_by_codemod[codemod_id] = None
return record
# pylint: disable-next=cyclic-import
from codemodder.dependency_management import DependencyManager
for package_store in store_list:
dm = DependencyManager(package_store, self.directory)
if (changeset := dm.write(list(dependencies), self.dry_run)) is not None:
self.add_results(codemod_id, [changeset])
self._dependency_update_by_codemod[codemod_id] = package_store
for dep in dependencies:
record[dep] = package_store
break
return record
def add_description(self, codemod: CodemodExecutorWrapper):
description = codemod.description
if dependencies := list(self.dependencies.get(codemod.id, [])):
if pkg_store := self._dependency_update_by_codemod.get(codemod.id):
description += build_dependency_notification(
pkg_store.type.value, dependencies[0]
)
else:
description += build_failed_dependency_notification(dependencies[0])
return description
def process_results(self, codemod_id: str, results: Iterator[FileContext]):
for file_context in results:
self.add_results(codemod_id, file_context.results)
self.add_failures(codemod_id, file_context.failures)
self.add_dependencies(codemod_id, file_context.dependencies)
self.timer.aggregate(file_context.timer)
def compile_results(self, codemods: list[CodemodExecutorWrapper]):
results = []
for codemod in codemods:
data = {
"codemod": codemod.id,
"summary": codemod.summary,
"description": self.add_description(codemod),
"references": codemod.references,
"properties": {},
"failedFiles": [str(file) for file in self.get_failures(codemod.id)],
"changeset": [
change.to_json() for change in self.get_results(codemod.id)
],
}
results.append(data)
return results
def log_changes(self, codemod_id: str):
if failures := self.get_failures(codemod_id):
log_list(logging.INFO, "failed", failures)
if changes := self.get_results(codemod_id):
logger.info("changed:")
for change in changes:
logger.info(" - %s", change.path)
logger.debug(" diff:\n%s", indent(change.diff, " " * 6))