grimp 3.19__tar.gz → 3.20__tar.gz
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.
- {grimp-3.19 → grimp-3.20}/PKG-INFO +1 -1
- {grimp-3.19 → grimp-3.20}/pyproject.toml +1 -1
- grimp-3.20/rust/src/graph/direct_import_queries.rs +328 -0
- {grimp-3.19 → grimp-3.20}/rust/src/graph/graph_manipulation.rs +4 -12
- {grimp-3.19 → grimp-3.20}/rust/src/graph/hierarchy_queries.rs +15 -6
- {grimp-3.19 → grimp-3.20}/rust/src/graph/mod.rs +14 -4
- {grimp-3.19 → grimp-3.20}/src/grimp/__init__.py +1 -1
- {grimp-3.19 → grimp-3.20}/src/grimp/application/graph.py +18 -4
- grimp-3.19/rust/src/graph/direct_import_queries.rs +0 -158
- {grimp-3.19 → grimp-3.20}/AUTHORS.rst +0 -0
- {grimp-3.19 → grimp-3.20}/LICENSE +0 -0
- {grimp-3.19 → grimp-3.20}/README.rst +0 -0
- {grimp-3.19 → grimp-3.20}/rust/Cargo.lock +0 -0
- {grimp-3.19 → grimp-3.20}/rust/Cargo.toml +0 -0
- {grimp-3.19 → grimp-3.20}/rust/build.rs +0 -0
- {grimp-3.19 → grimp-3.20}/rust/rust-toolchain.toml +0 -0
- {grimp-3.19 → grimp-3.20}/rust/src/caching.rs +0 -0
- {grimp-3.19 → grimp-3.20}/rust/src/errors.rs +0 -0
- {grimp-3.19 → grimp-3.20}/rust/src/exceptions.rs +0 -0
- {grimp-3.19 → grimp-3.20}/rust/src/filesystem.rs +0 -0
- {grimp-3.19 → grimp-3.20}/rust/src/graph/cycle_breakers.rs +0 -0
- {grimp-3.19 → grimp-3.20}/rust/src/graph/higher_order_queries.rs +0 -0
- {grimp-3.19 → grimp-3.20}/rust/src/graph/import_chain_queries.rs +0 -0
- {grimp-3.19 → grimp-3.20}/rust/src/graph/pathfinding.rs +0 -0
- {grimp-3.19 → grimp-3.20}/rust/src/import_parsing.rs +0 -0
- {grimp-3.19 → grimp-3.20}/rust/src/import_scanning.rs +0 -0
- {grimp-3.19 → grimp-3.20}/rust/src/lib.rs +0 -0
- {grimp-3.19 → grimp-3.20}/rust/src/module_expressions.rs +0 -0
- {grimp-3.19 → grimp-3.20}/rust/src/module_finding.rs +0 -0
- {grimp-3.19 → grimp-3.20}/rust/tests/large.rs +0 -0
- {grimp-3.19 → grimp-3.20}/rust/tests/large_graph.json +0 -0
- {grimp-3.19 → grimp-3.20}/src/grimp/adaptors/__init__.py +0 -0
- {grimp-3.19 → grimp-3.20}/src/grimp/adaptors/caching.py +0 -0
- {grimp-3.19 → grimp-3.20}/src/grimp/adaptors/filesystem.py +0 -0
- {grimp-3.19 → grimp-3.20}/src/grimp/adaptors/modulefinder.py +0 -0
- {grimp-3.19 → grimp-3.20}/src/grimp/adaptors/packagefinder.py +0 -0
- {grimp-3.19 → grimp-3.20}/src/grimp/adaptors/timing.py +0 -0
- {grimp-3.19 → grimp-3.20}/src/grimp/application/__init__.py +0 -0
- {grimp-3.19 → grimp-3.20}/src/grimp/application/config.py +0 -0
- {grimp-3.19 → grimp-3.20}/src/grimp/application/ports/__init__.py +0 -0
- {grimp-3.19 → grimp-3.20}/src/grimp/application/ports/caching.py +0 -0
- {grimp-3.19 → grimp-3.20}/src/grimp/application/ports/filesystem.py +0 -0
- {grimp-3.19 → grimp-3.20}/src/grimp/application/ports/modulefinder.py +0 -0
- {grimp-3.19 → grimp-3.20}/src/grimp/application/ports/packagefinder.py +0 -0
- {grimp-3.19 → grimp-3.20}/src/grimp/application/ports/timing.py +0 -0
- {grimp-3.19 → grimp-3.20}/src/grimp/application/scanning.py +0 -0
- {grimp-3.19 → grimp-3.20}/src/grimp/application/usecases.py +0 -0
- {grimp-3.19 → grimp-3.20}/src/grimp/domain/__init__.py +0 -0
- {grimp-3.19 → grimp-3.20}/src/grimp/domain/analysis.py +0 -0
- {grimp-3.19 → grimp-3.20}/src/grimp/domain/valueobjects.py +0 -0
- {grimp-3.19 → grimp-3.20}/src/grimp/exceptions.py +0 -0
- {grimp-3.19 → grimp-3.20}/src/grimp/main.py +0 -0
- {grimp-3.19 → grimp-3.20}/src/grimp/py.typed +0 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
use crate::errors::{GrimpError, GrimpResult};
|
|
2
|
+
use crate::graph::{
|
|
3
|
+
EMPTY_IMPORT_DETAILS, EMPTY_MODULE_TOKENS, ExtendWithDescendants, Graph, ModuleIterator,
|
|
4
|
+
ModuleToken, PyImportDetails,
|
|
5
|
+
};
|
|
6
|
+
use crate::module_expressions::ModuleExpression;
|
|
7
|
+
use rustc_hash::FxHashSet;
|
|
8
|
+
|
|
9
|
+
impl Graph {
|
|
10
|
+
pub fn count_imports(&self) -> usize {
|
|
11
|
+
self.imports.values().map(|imports| imports.len()).sum()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
pub fn direct_import_exists(
|
|
15
|
+
&self,
|
|
16
|
+
importer: ModuleToken,
|
|
17
|
+
imported: ModuleToken,
|
|
18
|
+
as_packages: bool,
|
|
19
|
+
) -> GrimpResult<bool> {
|
|
20
|
+
let mut importers: FxHashSet<_> = importer.into();
|
|
21
|
+
let mut importeds: FxHashSet<_> = imported.into();
|
|
22
|
+
if as_packages {
|
|
23
|
+
importers.extend_with_descendants(self);
|
|
24
|
+
importeds.extend_with_descendants(self);
|
|
25
|
+
if !(&importers & &importeds).is_empty() {
|
|
26
|
+
return Err(GrimpError::SharedDescendants);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let direct_imports = importers
|
|
31
|
+
.iter()
|
|
32
|
+
.flat_map(|importer_module| self.imports.get(*importer_module).unwrap().iter().cloned())
|
|
33
|
+
.collect::<FxHashSet<ModuleToken>>();
|
|
34
|
+
|
|
35
|
+
Ok(!(&direct_imports & &importeds).is_empty())
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
pub fn find_direct_imports_between(
|
|
39
|
+
&self,
|
|
40
|
+
importer: ModuleToken,
|
|
41
|
+
imported: ModuleToken,
|
|
42
|
+
as_packages: bool,
|
|
43
|
+
) -> GrimpResult<FxHashSet<(ModuleToken, ModuleToken)>> {
|
|
44
|
+
let mut all_imports: FxHashSet<(ModuleToken, ModuleToken)> = FxHashSet::default();
|
|
45
|
+
|
|
46
|
+
let mut importers: FxHashSet<_> = importer.into();
|
|
47
|
+
let mut importeds: FxHashSet<_> = imported.into();
|
|
48
|
+
if as_packages {
|
|
49
|
+
importers.extend_with_descendants(self);
|
|
50
|
+
importeds.extend_with_descendants(self);
|
|
51
|
+
if !(&importers & &importeds).is_empty() {
|
|
52
|
+
return Err(GrimpError::SharedDescendants);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
for importer_module in importers.iter() {
|
|
57
|
+
if let Some(all_imports_imported_by_this_one) = self.imports.get(*importer_module) {
|
|
58
|
+
let imports_of_supplied_package: FxHashSet<_> = all_imports_imported_by_this_one
|
|
59
|
+
.iter()
|
|
60
|
+
.filter(|candidate| importeds.contains(*candidate))
|
|
61
|
+
.map(|imported_module| (*importer_module, *imported_module))
|
|
62
|
+
.collect();
|
|
63
|
+
all_imports.extend(imports_of_supplied_package);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
Ok(all_imports)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/// Find the modules directly imported by the module.
|
|
71
|
+
///
|
|
72
|
+
/// If `as_package` is true, the module is treated as a package: the result is the modules
|
|
73
|
+
/// directly imported by it or any of its descendants, excluding modules within the package.
|
|
74
|
+
pub fn modules_directly_imported_by(
|
|
75
|
+
&self,
|
|
76
|
+
importer: ModuleToken,
|
|
77
|
+
as_package: bool,
|
|
78
|
+
) -> FxHashSet<ModuleToken> {
|
|
79
|
+
let mut importers: FxHashSet<_> = importer.into();
|
|
80
|
+
if as_package {
|
|
81
|
+
importers.extend_with_descendants(self);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let mut imported_modules: FxHashSet<_> = importers
|
|
85
|
+
.iter()
|
|
86
|
+
.flat_map(|importer| self.imports.get(*importer).unwrap_or(&EMPTY_MODULE_TOKENS))
|
|
87
|
+
.cloned()
|
|
88
|
+
.collect();
|
|
89
|
+
if as_package {
|
|
90
|
+
imported_modules.retain(|imported| !importers.contains(imported));
|
|
91
|
+
}
|
|
92
|
+
imported_modules
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/// Find the modules that directly import the module.
|
|
96
|
+
///
|
|
97
|
+
/// If `as_package` is true, the module is treated as a package: the result is the modules
|
|
98
|
+
/// that directly import it or any of its descendants, excluding modules within the package.
|
|
99
|
+
pub fn modules_that_directly_import(
|
|
100
|
+
&self,
|
|
101
|
+
imported: ModuleToken,
|
|
102
|
+
as_package: bool,
|
|
103
|
+
) -> FxHashSet<ModuleToken> {
|
|
104
|
+
let mut importeds: FxHashSet<_> = imported.into();
|
|
105
|
+
if as_package {
|
|
106
|
+
importeds.extend_with_descendants(self);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let mut importers: FxHashSet<_> = importeds
|
|
110
|
+
.iter()
|
|
111
|
+
.flat_map(|imported| {
|
|
112
|
+
self.reverse_imports
|
|
113
|
+
.get(*imported)
|
|
114
|
+
.unwrap_or(&EMPTY_MODULE_TOKENS)
|
|
115
|
+
})
|
|
116
|
+
.cloned()
|
|
117
|
+
.collect();
|
|
118
|
+
if as_package {
|
|
119
|
+
importers.retain(|importer| !importeds.contains(importer));
|
|
120
|
+
}
|
|
121
|
+
importers
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
pub fn get_import_details(
|
|
125
|
+
&self,
|
|
126
|
+
importer: ModuleToken,
|
|
127
|
+
imported: ModuleToken,
|
|
128
|
+
) -> &FxHashSet<PyImportDetails> {
|
|
129
|
+
match self.import_details.get(&(importer, imported)) {
|
|
130
|
+
Some(import_details) => import_details,
|
|
131
|
+
None => &EMPTY_IMPORT_DETAILS,
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
pub fn find_matching_direct_imports(
|
|
136
|
+
&self,
|
|
137
|
+
importer_expression: &ModuleExpression,
|
|
138
|
+
imported_expression: &ModuleExpression,
|
|
139
|
+
) -> FxHashSet<(ModuleToken, ModuleToken)> {
|
|
140
|
+
let importers: FxHashSet<ModuleToken> = self
|
|
141
|
+
.find_matching_modules(importer_expression)
|
|
142
|
+
.tokens()
|
|
143
|
+
.collect();
|
|
144
|
+
let importeds: FxHashSet<ModuleToken> = self
|
|
145
|
+
.find_matching_modules(imported_expression)
|
|
146
|
+
.tokens()
|
|
147
|
+
.collect();
|
|
148
|
+
|
|
149
|
+
importers
|
|
150
|
+
.iter()
|
|
151
|
+
.flat_map(|importer| {
|
|
152
|
+
self.modules_directly_imported_by(*importer, false)
|
|
153
|
+
.into_iter()
|
|
154
|
+
.filter(|imported| importeds.contains(imported))
|
|
155
|
+
.map(move |imported| (*importer, imported))
|
|
156
|
+
})
|
|
157
|
+
.collect()
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
#[cfg(test)]
|
|
162
|
+
mod test {
|
|
163
|
+
use super::*;
|
|
164
|
+
|
|
165
|
+
#[test]
|
|
166
|
+
fn test_modules_directly_imported_by() {
|
|
167
|
+
let mut graph = Graph::default();
|
|
168
|
+
|
|
169
|
+
let animals = graph.get_or_add_module("pkg.animals").token;
|
|
170
|
+
let dog = graph.get_or_add_module("pkg.animals.dog").token;
|
|
171
|
+
let cat = graph.get_or_add_module("pkg.animals.cat").token;
|
|
172
|
+
let food = graph.get_or_add_module("pkg.food").token;
|
|
173
|
+
let chicken = graph.get_or_add_module("pkg.food.chicken").token;
|
|
174
|
+
let tesco = graph.get_or_add_module("pkg.shops.tesco").token;
|
|
175
|
+
|
|
176
|
+
graph.add_import(dog, chicken);
|
|
177
|
+
graph.add_import(cat, food);
|
|
178
|
+
// An import within the package.
|
|
179
|
+
graph.add_import(dog, cat);
|
|
180
|
+
// An import of the package, not by it.
|
|
181
|
+
graph.add_import(tesco, animals);
|
|
182
|
+
|
|
183
|
+
// Without as_package, only the module itself is looked at.
|
|
184
|
+
assert_eq!(
|
|
185
|
+
graph.modules_directly_imported_by(animals, false),
|
|
186
|
+
FxHashSet::default()
|
|
187
|
+
);
|
|
188
|
+
assert_eq!(
|
|
189
|
+
graph.modules_directly_imported_by(dog, false),
|
|
190
|
+
FxHashSet::from_iter([chicken, cat])
|
|
191
|
+
);
|
|
192
|
+
// With as_package, descendants are looked at too, but modules within the package
|
|
193
|
+
// are excluded.
|
|
194
|
+
assert_eq!(
|
|
195
|
+
graph.modules_directly_imported_by(animals, true),
|
|
196
|
+
FxHashSet::from_iter([chicken, food])
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
#[test]
|
|
201
|
+
fn test_modules_that_directly_import() {
|
|
202
|
+
let mut graph = Graph::default();
|
|
203
|
+
|
|
204
|
+
let animals = graph.get_or_add_module("pkg.animals").token;
|
|
205
|
+
let dog = graph.get_or_add_module("pkg.animals.dog").token;
|
|
206
|
+
let cat = graph.get_or_add_module("pkg.animals.cat").token;
|
|
207
|
+
let chicken = graph.get_or_add_module("pkg.food.chicken").token;
|
|
208
|
+
let shops = graph.get_or_add_module("pkg.shops").token;
|
|
209
|
+
let tesco = graph.get_or_add_module("pkg.shops.tesco").token;
|
|
210
|
+
|
|
211
|
+
graph.add_import(tesco, animals);
|
|
212
|
+
graph.add_import(shops, cat);
|
|
213
|
+
// An import within the package.
|
|
214
|
+
graph.add_import(dog, cat);
|
|
215
|
+
// An import by the package, not of it.
|
|
216
|
+
graph.add_import(dog, chicken);
|
|
217
|
+
|
|
218
|
+
// Without as_package, only the module itself is looked at.
|
|
219
|
+
assert_eq!(
|
|
220
|
+
graph.modules_that_directly_import(animals, false),
|
|
221
|
+
FxHashSet::from_iter([tesco])
|
|
222
|
+
);
|
|
223
|
+
assert_eq!(
|
|
224
|
+
graph.modules_that_directly_import(cat, false),
|
|
225
|
+
FxHashSet::from_iter([shops, dog])
|
|
226
|
+
);
|
|
227
|
+
// With as_package, descendants are looked at too, but modules within the package
|
|
228
|
+
// are excluded.
|
|
229
|
+
assert_eq!(
|
|
230
|
+
graph.modules_that_directly_import(animals, true),
|
|
231
|
+
FxHashSet::from_iter([tesco, shops])
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
#[test]
|
|
236
|
+
fn test_find_matching_direct_imports() {
|
|
237
|
+
let mut graph = Graph::default();
|
|
238
|
+
|
|
239
|
+
let _ = graph.get_or_add_module("pkg.animals").token;
|
|
240
|
+
let dog = graph.get_or_add_module("pkg.animals.dog").token;
|
|
241
|
+
let cat = graph.get_or_add_module("pkg.animals.cat").token;
|
|
242
|
+
let _ = graph.get_or_add_module("pkg.food").token;
|
|
243
|
+
let chicken = graph.get_or_add_module("pkg.food.chicken").token;
|
|
244
|
+
let fish = graph.get_or_add_module("pkg.food.fish").token;
|
|
245
|
+
let _ = graph.get_or_add_module("pkg.colors").token;
|
|
246
|
+
let golden = graph.get_or_add_module("pkg.colors.golden").token;
|
|
247
|
+
let ginger = graph.get_or_add_module("pkg.colors.ginger").token;
|
|
248
|
+
let _ = graph.get_or_add_module("pkg.shops").token;
|
|
249
|
+
let tesco = graph.get_or_add_module("pkg.shops.tesco").token;
|
|
250
|
+
let coop = graph.get_or_add_module("pkg.shops.coop").token;
|
|
251
|
+
|
|
252
|
+
// Should match
|
|
253
|
+
graph.add_import(dog, chicken);
|
|
254
|
+
graph.add_import(cat, fish);
|
|
255
|
+
// Should not match: Imported does not match
|
|
256
|
+
graph.add_import(dog, golden);
|
|
257
|
+
graph.add_import(cat, ginger);
|
|
258
|
+
// Should not match: Importer does not match
|
|
259
|
+
graph.add_import(tesco, chicken);
|
|
260
|
+
graph.add_import(coop, fish);
|
|
261
|
+
|
|
262
|
+
let importer_expression = "pkg.animals.*".parse().unwrap();
|
|
263
|
+
let imported_expression = "pkg.food.*".parse().unwrap();
|
|
264
|
+
let matching_imports =
|
|
265
|
+
graph.find_matching_direct_imports(&importer_expression, &imported_expression);
|
|
266
|
+
|
|
267
|
+
assert_eq!(
|
|
268
|
+
matching_imports,
|
|
269
|
+
FxHashSet::from_iter([(dog, chicken), (cat, fish)])
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
#[test]
|
|
274
|
+
fn test_find_matching_direct_imports_with_and_without_wildcards() {
|
|
275
|
+
let mut graph = Graph::default();
|
|
276
|
+
|
|
277
|
+
let dog = graph.get_or_add_module("pkg.animals.dog").token;
|
|
278
|
+
let cat = graph.get_or_add_module("pkg.animals.cat").token;
|
|
279
|
+
let chicken = graph.get_or_add_module("pkg.food.chicken").token;
|
|
280
|
+
let fish = graph.get_or_add_module("pkg.food.fish").token;
|
|
281
|
+
let golden = graph.get_or_add_module("pkg.colors.golden").token;
|
|
282
|
+
let tesco = graph.get_or_add_module("pkg.shops.tesco").token;
|
|
283
|
+
graph.get_or_add_module("pkg.shops.coop");
|
|
284
|
+
|
|
285
|
+
graph.add_import(dog, chicken);
|
|
286
|
+
graph.add_import(dog, golden);
|
|
287
|
+
graph.add_import(cat, fish);
|
|
288
|
+
graph.add_import(tesco, chicken);
|
|
289
|
+
|
|
290
|
+
let find = |importer_expression: &str, imported_expression: &str| {
|
|
291
|
+
graph.find_matching_direct_imports(
|
|
292
|
+
&importer_expression.parse().unwrap(),
|
|
293
|
+
&imported_expression.parse().unwrap(),
|
|
294
|
+
)
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
// Neither side has wildcards.
|
|
298
|
+
assert_eq!(
|
|
299
|
+
find("pkg.animals.dog", "pkg.food.chicken"),
|
|
300
|
+
FxHashSet::from_iter([(dog, chicken)])
|
|
301
|
+
);
|
|
302
|
+
// Only the importer is a single module.
|
|
303
|
+
assert_eq!(
|
|
304
|
+
find("pkg.animals.dog", "pkg.*.*"),
|
|
305
|
+
FxHashSet::from_iter([(dog, chicken), (dog, golden)])
|
|
306
|
+
);
|
|
307
|
+
// Only the imported is a single module.
|
|
308
|
+
assert_eq!(
|
|
309
|
+
find("pkg.*.*", "pkg.food.chicken"),
|
|
310
|
+
FxHashSet::from_iter([(dog, chicken), (tesco, chicken)])
|
|
311
|
+
);
|
|
312
|
+
// Both sides have wildcards.
|
|
313
|
+
assert_eq!(
|
|
314
|
+
find("pkg.shops.*", "pkg.food.*"),
|
|
315
|
+
FxHashSet::from_iter([(tesco, chicken)])
|
|
316
|
+
);
|
|
317
|
+
// A module with no imports.
|
|
318
|
+
assert!(find("pkg.shops.coop", "pkg.*.*").is_empty());
|
|
319
|
+
// A module that has no importers.
|
|
320
|
+
assert!(find("pkg.*.*", "pkg.shops.coop").is_empty());
|
|
321
|
+
// Modules that aren't in the graph.
|
|
322
|
+
assert!(find("pkg.animals.missing", "pkg.*.*").is_empty());
|
|
323
|
+
assert!(find("pkg.*.*", "pkg.food.missing").is_empty());
|
|
324
|
+
assert!(find("pkg.animals.missing", "pkg.food.missing").is_empty());
|
|
325
|
+
// Wildcards that match nothing.
|
|
326
|
+
assert!(find("pkg.nothing.*", "pkg.*.*").is_empty());
|
|
327
|
+
}
|
|
328
|
+
}
|
|
@@ -91,10 +91,10 @@ impl Graph {
|
|
|
91
91
|
self.module_children.remove(module);
|
|
92
92
|
|
|
93
93
|
// Update imports.
|
|
94
|
-
for imported in self.modules_directly_imported_by(module)
|
|
94
|
+
for imported in self.modules_directly_imported_by(module, false) {
|
|
95
95
|
self.remove_import(module, imported);
|
|
96
96
|
}
|
|
97
|
-
for importer in self.modules_that_directly_import(module)
|
|
97
|
+
for importer in self.modules_that_directly_import(module, false) {
|
|
98
98
|
self.remove_import(importer, module);
|
|
99
99
|
}
|
|
100
100
|
self.imports.remove(module);
|
|
@@ -164,19 +164,11 @@ impl Graph {
|
|
|
164
164
|
|
|
165
165
|
let modules_imported_by_descendants: FxHashSet<_> = descendants
|
|
166
166
|
.iter()
|
|
167
|
-
.flat_map(|descendant|
|
|
168
|
-
self.modules_directly_imported_by(*descendant)
|
|
169
|
-
.iter()
|
|
170
|
-
.cloned()
|
|
171
|
-
})
|
|
167
|
+
.flat_map(|descendant| self.modules_directly_imported_by(*descendant, false))
|
|
172
168
|
.collect();
|
|
173
169
|
let modules_that_import_descendants: FxHashSet<_> = descendants
|
|
174
170
|
.iter()
|
|
175
|
-
.flat_map(|descendant|
|
|
176
|
-
self.modules_that_directly_import(*descendant)
|
|
177
|
-
.iter()
|
|
178
|
-
.cloned()
|
|
179
|
-
})
|
|
171
|
+
.flat_map(|descendant| self.modules_that_directly_import(*descendant, false))
|
|
180
172
|
.collect();
|
|
181
173
|
|
|
182
174
|
// Add descendants and imports to parent module.
|
|
@@ -54,12 +54,19 @@ impl Graph {
|
|
|
54
54
|
&self,
|
|
55
55
|
expression: &ModuleExpression,
|
|
56
56
|
) -> impl ModuleIterator<'_> + use<'_> {
|
|
57
|
-
let
|
|
58
|
-
|
|
59
|
-
.modules
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
57
|
+
let modules: FxHashSet<_> = if expression.as_ref().contains('*') {
|
|
58
|
+
let interner = MODULE_NAMES.read().unwrap();
|
|
59
|
+
self.modules
|
|
60
|
+
.values()
|
|
61
|
+
.filter(|m| expression.is_match(interner.resolve(m.interned_name).unwrap()))
|
|
62
|
+
.collect()
|
|
63
|
+
} else {
|
|
64
|
+
// Without wildcards the expression can only match the module with that name,
|
|
65
|
+
// so there's no need to check every module.
|
|
66
|
+
self.get_module_by_name(expression.as_ref())
|
|
67
|
+
.into_iter()
|
|
68
|
+
.collect()
|
|
69
|
+
};
|
|
63
70
|
modules.into_iter()
|
|
64
71
|
}
|
|
65
72
|
}
|
|
@@ -80,6 +87,8 @@ mod tests {
|
|
|
80
87
|
|
|
81
88
|
#[parameterized(case = {
|
|
82
89
|
FindMatchingModulesTestCase::new("foo", ["foo"]),
|
|
90
|
+
FindMatchingModulesTestCase::new("foo.bar.baz", ["foo.bar.baz"]),
|
|
91
|
+
FindMatchingModulesTestCase::new("foo.missing", Vec::<&str>::new()),
|
|
83
92
|
FindMatchingModulesTestCase::new("foo.*", ["foo.bar"]),
|
|
84
93
|
FindMatchingModulesTestCase::new("foo.**", ["foo.bar", "foo.bar.baz"]),
|
|
85
94
|
})]
|
|
@@ -394,11 +394,16 @@ impl GraphWrapper {
|
|
|
394
394
|
.direct_import_exists(importer, imported, as_packages)?)
|
|
395
395
|
}
|
|
396
396
|
|
|
397
|
-
|
|
397
|
+
#[pyo3(signature = (module, as_package=false))]
|
|
398
|
+
pub fn find_modules_directly_imported_by(
|
|
399
|
+
&self,
|
|
400
|
+
module: &str,
|
|
401
|
+
as_package: bool,
|
|
402
|
+
) -> PyResult<HashSet<String>> {
|
|
398
403
|
let module = self.get_visible_module_by_name(module)?.token();
|
|
399
404
|
Ok(self
|
|
400
405
|
._graph
|
|
401
|
-
.modules_directly_imported_by(module)
|
|
406
|
+
.modules_directly_imported_by(module, as_package)
|
|
402
407
|
.iter()
|
|
403
408
|
.into_module_iterator(&self._graph)
|
|
404
409
|
.visible()
|
|
@@ -406,11 +411,16 @@ impl GraphWrapper {
|
|
|
406
411
|
.collect())
|
|
407
412
|
}
|
|
408
413
|
|
|
409
|
-
|
|
414
|
+
#[pyo3(signature = (module, as_package=false))]
|
|
415
|
+
pub fn find_modules_that_directly_import(
|
|
416
|
+
&self,
|
|
417
|
+
module: &str,
|
|
418
|
+
as_package: bool,
|
|
419
|
+
) -> PyResult<HashSet<String>> {
|
|
410
420
|
let module = self.get_visible_module_by_name(module)?.token();
|
|
411
421
|
Ok(self
|
|
412
422
|
._graph
|
|
413
|
-
.modules_that_directly_import(module)
|
|
423
|
+
.modules_that_directly_import(module, as_package)
|
|
414
424
|
.iter()
|
|
415
425
|
.into_module_iterator(&self._graph)
|
|
416
426
|
.visible()
|
|
@@ -223,27 +223,41 @@ class ImportGraph:
|
|
|
223
223
|
importer=importer, imported=imported, as_packages=as_packages
|
|
224
224
|
)
|
|
225
225
|
|
|
226
|
-
def find_modules_directly_imported_by(self, module: str) -> set[str]:
|
|
226
|
+
def find_modules_directly_imported_by(self, module: str, as_package: bool = False) -> set[str]:
|
|
227
227
|
"""
|
|
228
228
|
Find all modules that are directly imported by the supplied module.
|
|
229
229
|
|
|
230
|
+
Args:
|
|
231
|
+
module: The name of the importing module.
|
|
232
|
+
as_package: Whether or not to treat the supplied module as an individual module,
|
|
233
|
+
or as a package (i.e. including any descendants, if there are any). If
|
|
234
|
+
treating it as a package, the result will include modules *external* to
|
|
235
|
+
the package, and won't include modules within it.
|
|
236
|
+
|
|
230
237
|
If the module is not present in the graph, grimp.exceptions.ModuleNotPresent will be raised.
|
|
231
238
|
"""
|
|
232
239
|
if not self._rustgraph.contains_module(module):
|
|
233
240
|
msg = f'"{module}" not present in the graph.'
|
|
234
241
|
raise ModuleNotPresent(msg)
|
|
235
|
-
return self._rustgraph.find_modules_directly_imported_by(module)
|
|
242
|
+
return self._rustgraph.find_modules_directly_imported_by(module, as_package)
|
|
236
243
|
|
|
237
|
-
def find_modules_that_directly_import(self, module: str) -> set[str]:
|
|
244
|
+
def find_modules_that_directly_import(self, module: str, as_package: bool = False) -> set[str]:
|
|
238
245
|
"""
|
|
239
246
|
Find all modules that directly import the supplied module.
|
|
240
247
|
|
|
248
|
+
Args:
|
|
249
|
+
module: The name of the imported module.
|
|
250
|
+
as_package: Whether or not to treat the supplied module as an individual module,
|
|
251
|
+
or as a package (i.e. including any descendants, if there are any). If
|
|
252
|
+
treating it as a package, the result will include modules *external* to
|
|
253
|
+
the package, and won't include modules within it.
|
|
254
|
+
|
|
241
255
|
If the module is not present in the graph, grimp.exceptions.ModuleNotPresent will be raised.
|
|
242
256
|
"""
|
|
243
257
|
if not self._rustgraph.contains_module(module):
|
|
244
258
|
msg = f'"{module}" not present in the graph.'
|
|
245
259
|
raise ModuleNotPresent(msg)
|
|
246
|
-
return self._rustgraph.find_modules_that_directly_import(module)
|
|
260
|
+
return self._rustgraph.find_modules_that_directly_import(module, as_package)
|
|
247
261
|
|
|
248
262
|
def get_import_details(self, *, importer: str, imported: str) -> list[DetailedImport]:
|
|
249
263
|
"""
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
use crate::errors::{GrimpError, GrimpResult};
|
|
2
|
-
use crate::graph::{
|
|
3
|
-
EMPTY_IMPORT_DETAILS, EMPTY_MODULE_TOKENS, ExtendWithDescendants, Graph, MODULE_NAMES,
|
|
4
|
-
ModuleToken, PyImportDetails,
|
|
5
|
-
};
|
|
6
|
-
use crate::module_expressions::ModuleExpression;
|
|
7
|
-
use rustc_hash::FxHashSet;
|
|
8
|
-
|
|
9
|
-
impl Graph {
|
|
10
|
-
pub fn count_imports(&self) -> usize {
|
|
11
|
-
self.imports.values().map(|imports| imports.len()).sum()
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
pub fn direct_import_exists(
|
|
15
|
-
&self,
|
|
16
|
-
importer: ModuleToken,
|
|
17
|
-
imported: ModuleToken,
|
|
18
|
-
as_packages: bool,
|
|
19
|
-
) -> GrimpResult<bool> {
|
|
20
|
-
let mut importers: FxHashSet<_> = importer.into();
|
|
21
|
-
let mut importeds: FxHashSet<_> = imported.into();
|
|
22
|
-
if as_packages {
|
|
23
|
-
importers.extend_with_descendants(self);
|
|
24
|
-
importeds.extend_with_descendants(self);
|
|
25
|
-
if !(&importers & &importeds).is_empty() {
|
|
26
|
-
return Err(GrimpError::SharedDescendants);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
let direct_imports = importers
|
|
31
|
-
.iter()
|
|
32
|
-
.flat_map(|importer_module| self.imports.get(*importer_module).unwrap().iter().cloned())
|
|
33
|
-
.collect::<FxHashSet<ModuleToken>>();
|
|
34
|
-
|
|
35
|
-
Ok(!(&direct_imports & &importeds).is_empty())
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
pub fn find_direct_imports_between(
|
|
39
|
-
&self,
|
|
40
|
-
importer: ModuleToken,
|
|
41
|
-
imported: ModuleToken,
|
|
42
|
-
as_packages: bool,
|
|
43
|
-
) -> GrimpResult<FxHashSet<(ModuleToken, ModuleToken)>> {
|
|
44
|
-
let mut all_imports: FxHashSet<(ModuleToken, ModuleToken)> = FxHashSet::default();
|
|
45
|
-
|
|
46
|
-
let mut importers: FxHashSet<_> = importer.into();
|
|
47
|
-
let mut importeds: FxHashSet<_> = imported.into();
|
|
48
|
-
if as_packages {
|
|
49
|
-
importers.extend_with_descendants(self);
|
|
50
|
-
importeds.extend_with_descendants(self);
|
|
51
|
-
if !(&importers & &importeds).is_empty() {
|
|
52
|
-
return Err(GrimpError::SharedDescendants);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
for importer_module in importers.iter() {
|
|
57
|
-
if let Some(all_imports_imported_by_this_one) = self.imports.get(*importer_module) {
|
|
58
|
-
let imports_of_supplied_package: FxHashSet<_> = all_imports_imported_by_this_one
|
|
59
|
-
.iter()
|
|
60
|
-
.filter(|candidate| importeds.contains(*candidate))
|
|
61
|
-
.map(|imported_module| (*importer_module, *imported_module))
|
|
62
|
-
.collect();
|
|
63
|
-
all_imports.extend(imports_of_supplied_package);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
Ok(all_imports)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
pub fn modules_directly_imported_by(&self, importer: ModuleToken) -> &FxHashSet<ModuleToken> {
|
|
71
|
-
self.imports.get(importer).unwrap_or(&EMPTY_MODULE_TOKENS)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
pub fn modules_that_directly_import(&self, imported: ModuleToken) -> &FxHashSet<ModuleToken> {
|
|
75
|
-
self.reverse_imports
|
|
76
|
-
.get(imported)
|
|
77
|
-
.unwrap_or(&EMPTY_MODULE_TOKENS)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
pub fn get_import_details(
|
|
81
|
-
&self,
|
|
82
|
-
importer: ModuleToken,
|
|
83
|
-
imported: ModuleToken,
|
|
84
|
-
) -> &FxHashSet<PyImportDetails> {
|
|
85
|
-
match self.import_details.get(&(importer, imported)) {
|
|
86
|
-
Some(import_details) => import_details,
|
|
87
|
-
None => &EMPTY_IMPORT_DETAILS,
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
pub fn find_matching_direct_imports(
|
|
92
|
-
&self,
|
|
93
|
-
importer_expression: &ModuleExpression,
|
|
94
|
-
imported_expression: &ModuleExpression,
|
|
95
|
-
) -> FxHashSet<(ModuleToken, ModuleToken)> {
|
|
96
|
-
let interner = MODULE_NAMES.read().unwrap();
|
|
97
|
-
self.imports
|
|
98
|
-
.iter()
|
|
99
|
-
.flat_map(|(importer, imports)| {
|
|
100
|
-
imports
|
|
101
|
-
.iter()
|
|
102
|
-
.cloned()
|
|
103
|
-
.map(move |imported| (importer, imported))
|
|
104
|
-
})
|
|
105
|
-
.filter(|(importer, imported)| {
|
|
106
|
-
let importer = self.get_module(*importer).unwrap();
|
|
107
|
-
let importer_name = interner.resolve(importer.interned_name).unwrap();
|
|
108
|
-
let imported = self.get_module(*imported).unwrap();
|
|
109
|
-
let imported_name = interner.resolve(imported.interned_name).unwrap();
|
|
110
|
-
importer_expression.is_match(importer_name)
|
|
111
|
-
&& imported_expression.is_match(imported_name)
|
|
112
|
-
})
|
|
113
|
-
.collect()
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
#[cfg(test)]
|
|
118
|
-
mod test {
|
|
119
|
-
use super::*;
|
|
120
|
-
|
|
121
|
-
#[test]
|
|
122
|
-
fn test_find_matching_direct_imports() {
|
|
123
|
-
let mut graph = Graph::default();
|
|
124
|
-
|
|
125
|
-
let _ = graph.get_or_add_module("pkg.animals").token;
|
|
126
|
-
let dog = graph.get_or_add_module("pkg.animals.dog").token;
|
|
127
|
-
let cat = graph.get_or_add_module("pkg.animals.cat").token;
|
|
128
|
-
let _ = graph.get_or_add_module("pkg.food").token;
|
|
129
|
-
let chicken = graph.get_or_add_module("pkg.food.chicken").token;
|
|
130
|
-
let fish = graph.get_or_add_module("pkg.food.fish").token;
|
|
131
|
-
let _ = graph.get_or_add_module("pkg.colors").token;
|
|
132
|
-
let golden = graph.get_or_add_module("pkg.colors.golden").token;
|
|
133
|
-
let ginger = graph.get_or_add_module("pkg.colors.ginger").token;
|
|
134
|
-
let _ = graph.get_or_add_module("pkg.shops").token;
|
|
135
|
-
let tesco = graph.get_or_add_module("pkg.shops.tesco").token;
|
|
136
|
-
let coop = graph.get_or_add_module("pkg.shops.coop").token;
|
|
137
|
-
|
|
138
|
-
// Should match
|
|
139
|
-
graph.add_import(dog, chicken);
|
|
140
|
-
graph.add_import(cat, fish);
|
|
141
|
-
// Should not match: Imported does not match
|
|
142
|
-
graph.add_import(dog, golden);
|
|
143
|
-
graph.add_import(cat, ginger);
|
|
144
|
-
// Should not match: Importer does not match
|
|
145
|
-
graph.add_import(tesco, chicken);
|
|
146
|
-
graph.add_import(coop, fish);
|
|
147
|
-
|
|
148
|
-
let importer_expression = "pkg.animals.*".parse().unwrap();
|
|
149
|
-
let imported_expression = "pkg.food.*".parse().unwrap();
|
|
150
|
-
let matching_imports =
|
|
151
|
-
graph.find_matching_direct_imports(&importer_expression, &imported_expression);
|
|
152
|
-
|
|
153
|
-
assert_eq!(
|
|
154
|
-
matching_imports,
|
|
155
|
-
FxHashSet::from_iter([(dog, chicken), (cat, fish)])
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|