Skip to main content

depyler_tooling/
module_mapper.rs

1//! Module mapping from Python to Rust equivalents
2
3use depyler_hir::hir::{Import, ImportItem};
4use std::collections::HashMap;
5
6#[cfg(test)]
7#[path = "module_mapper_tests.rs"]
8mod tests;
9
10/// Maps Python modules/packages to their Rust equivalents
11pub struct ModuleMapper {
12    /// Mapping from Python module names to Rust crate/module paths
13    module_map: HashMap<String, ModuleMapping>,
14}
15
16/// DEPYLER-0493: Constructor pattern for Rust types
17#[derive(Debug, Clone, PartialEq)]
18pub enum ConstructorPattern {
19    /// Call as ::new() - most common pattern (BufReader, NamedTempFile, etc.)
20    New,
21    /// Call as regular function - not a struct (e.g., tempfile::tempfile())
22    Function,
23    /// Custom method call (e.g., File::open(), Regex::compile())
24    Method(String),
25}
26
27#[derive(Debug, Clone)]
28pub struct ModuleMapping {
29    /// The Rust crate or module path
30    pub rust_path: String,
31    /// Whether this requires an external crate dependency
32    pub is_external: bool,
33    /// Optional crate version requirement
34    pub version: Option<String>,
35    /// Item-specific mappings within the module
36    pub item_map: HashMap<String, String>,
37    /// DEPYLER-0493: Constructor patterns for items that are types (not functions)
38    /// Maps item name to how it should be constructed
39    pub constructor_patterns: HashMap<String, ConstructorPattern>,
40}
41
42impl ModuleMapper {
43    /// Create a new module mapper with default Python to Rust mappings
44    ///
45    /// # Examples
46    ///
47    /// ```rust
48    /// use depyler_core::module_mapper::ModuleMapper;
49    ///
50    /// let mapper = ModuleMapper::new();
51    /// assert!(mapper.get_mapping("os").is_some());
52    /// assert!(mapper.get_mapping("json").is_some());
53    /// ```
54    pub fn new() -> Self {
55        let mut module_map = HashMap::new();
56
57        // Standard library mappings
58        module_map.insert(
59            "os".to_string(),
60            ModuleMapping {
61                rust_path: "std".to_string(),
62                is_external: false,
63                version: None,
64                item_map: HashMap::from([
65                    ("getcwd".to_string(), "env::current_dir".to_string()),
66                    ("environ".to_string(), "env::vars".to_string()),
67                    ("path".to_string(), "path::Path".to_string()),
68                    ("getenv".to_string(), "env::var".to_string()),
69                ]),
70                constructor_patterns: HashMap::new(),
71            },
72        );
73
74        module_map.insert(
75            "os.path".to_string(),
76            ModuleMapping {
77                rust_path: "std::path".to_string(),
78                is_external: false,
79                version: None,
80                item_map: HashMap::from([
81                    ("join".to_string(), "Path::join".to_string()),
82                    ("exists".to_string(), "Path::exists".to_string()),
83                    ("basename".to_string(), "Path::file_name".to_string()),
84                    ("dirname".to_string(), "Path::parent".to_string()),
85                    // DEPYLER-0721: splitext is handled inline in expr_gen.rs
86                    // Mark as Path to suppress invalid use statement
87                    ("splitext".to_string(), "Path".to_string()),
88                    ("split".to_string(), "Path".to_string()),
89                    ("normpath".to_string(), "Path".to_string()),
90                    ("isfile".to_string(), "Path::is_file".to_string()),
91                    ("isdir".to_string(), "Path::is_dir".to_string()),
92                    ("isabs".to_string(), "Path::is_absolute".to_string()),
93                    ("abspath".to_string(), "Path::canonicalize".to_string()),
94                ]),
95                constructor_patterns: HashMap::new(),
96            },
97        );
98
99        module_map.insert(
100            "sys".to_string(),
101            ModuleMapping {
102                rust_path: "std".to_string(),
103                is_external: false,
104                version: None,
105                item_map: HashMap::from([
106                    ("argv".to_string(), "env::args".to_string()),
107                    ("exit".to_string(), "process::exit".to_string()),
108                    ("stdin".to_string(), "io::stdin".to_string()),
109                    ("stdout".to_string(), "io::stdout".to_string()),
110                    ("stderr".to_string(), "io::stderr".to_string()),
111                ]),
112                constructor_patterns: HashMap::new(),
113            },
114        );
115
116        // DEPYLER-0493: Python io module → Rust std::io
117        module_map.insert(
118            "io".to_string(),
119            ModuleMapping {
120                rust_path: "std::io".to_string(),
121                is_external: false,
122                version: None,
123                item_map: HashMap::from([
124                    ("BufferedReader".to_string(), "BufReader".to_string()),
125                    ("BufferedWriter".to_string(), "BufWriter".to_string()),
126                    ("BytesIO".to_string(), "Cursor".to_string()),
127                    ("StringIO".to_string(), "Cursor".to_string()),
128                ]),
129                // DEPYLER-0493: Constructor patterns for IO types
130                constructor_patterns: HashMap::from([
131                    // BufReader and BufWriter use ::new(inner) pattern
132                    ("BufReader".to_string(), ConstructorPattern::New),
133                    ("BufWriter".to_string(), ConstructorPattern::New),
134                    // Cursor also uses ::new()
135                    ("Cursor".to_string(), ConstructorPattern::New),
136                ]),
137            },
138        );
139
140        module_map.insert(
141            "json".to_string(),
142            ModuleMapping {
143                rust_path: "serde_json".to_string(),
144                is_external: true,
145                version: Some("1.0".to_string()),
146                item_map: HashMap::from([
147                    ("loads".to_string(), "from_str".to_string()),
148                    ("dumps".to_string(), "to_string".to_string()),
149                    ("load".to_string(), "from_reader".to_string()),
150                    ("dump".to_string(), "to_writer".to_string()),
151                ]),
152                constructor_patterns: HashMap::new(),
153            },
154        );
155
156        // DEPYLER-EXTDEPS-001: Enhanced re → regex mapping
157        module_map.insert(
158            "re".to_string(),
159            ModuleMapping {
160                rust_path: "regex".to_string(),
161                is_external: true,
162                version: Some("1.10".to_string()),
163                item_map: HashMap::from([
164                    // Core functions
165                    ("compile".to_string(), "Regex::new".to_string()),
166                    ("search".to_string(), "Regex::find".to_string()),
167                    ("match".to_string(), "Regex::is_match".to_string()),
168                    ("findall".to_string(), "Regex::find_iter".to_string()),
169                    ("finditer".to_string(), "Regex::find_iter".to_string()),
170                    ("Pattern".to_string(), "Regex".to_string()),
171                    // Replacement operations
172                    ("sub".to_string(), "Regex::replace_all".to_string()),
173                    ("subn".to_string(), "Regex::replace_all".to_string()),
174                    // Split operations
175                    ("split".to_string(), "Regex::split".to_string()),
176                    // Flags - mapped to RegexBuilder methods or inline patterns
177                    ("IGNORECASE".to_string(), "(?i)".to_string()),
178                    ("I".to_string(), "(?i)".to_string()),
179                    ("MULTILINE".to_string(), "(?m)".to_string()),
180                    ("M".to_string(), "(?m)".to_string()),
181                    ("DOTALL".to_string(), "(?s)".to_string()),
182                    ("S".to_string(), "(?s)".to_string()),
183                    ("VERBOSE".to_string(), "(?x)".to_string()),
184                    ("X".to_string(), "(?x)".to_string()),
185                ]),
186                // GH-204: Add constructor patterns for regex types
187                constructor_patterns: HashMap::from([(
188                    "Regex".to_string(),
189                    ConstructorPattern::Method("new".to_string()),
190                )]),
191            },
192        );
193
194        module_map.insert(
195            "datetime".to_string(),
196            ModuleMapping {
197                rust_path: "chrono".to_string(),
198                is_external: true,
199                version: Some("0.4".to_string()),
200                item_map: HashMap::from([
201                    ("datetime".to_string(), "DateTime".to_string()),
202                    ("date".to_string(), "NaiveDate".to_string()),
203                    ("time".to_string(), "NaiveTime".to_string()),
204                    ("timedelta".to_string(), "Duration".to_string()),
205                ]),
206                // GH-204: Add constructor patterns for datetime types
207                constructor_patterns: HashMap::from([
208                    (
209                        "DateTime".to_string(),
210                        ConstructorPattern::Method("now".to_string()),
211                    ),
212                    (
213                        "NaiveDate".to_string(),
214                        ConstructorPattern::Method("from_ymd_opt".to_string()),
215                    ),
216                    (
217                        "NaiveTime".to_string(),
218                        ConstructorPattern::Method("from_hms_opt".to_string()),
219                    ),
220                    (
221                        "Duration".to_string(),
222                        ConstructorPattern::Method("seconds".to_string()),
223                    ),
224                ]),
225            },
226        );
227
228        module_map.insert(
229            "typing".to_string(),
230            ModuleMapping {
231                rust_path: "".to_string(), // No direct mapping, handled by type system
232                is_external: false,
233                version: None,
234                item_map: HashMap::from([
235                    ("List".to_string(), "Vec".to_string()),
236                    ("Dict".to_string(), "HashMap".to_string()),
237                    ("Set".to_string(), "HashSet".to_string()),
238                    ("Tuple".to_string(), "".to_string()), // Tuples are built-in
239                    ("Optional".to_string(), "Option".to_string()),
240                    ("Union".to_string(), "".to_string()), // Handled specially
241                    ("Any".to_string(), "".to_string()),   // No direct mapping
242                ]),
243                constructor_patterns: HashMap::new(),
244            },
245        );
246
247        module_map.insert(
248            "collections".to_string(),
249            ModuleMapping {
250                rust_path: "std::collections".to_string(),
251                is_external: false,
252                version: None,
253                item_map: HashMap::from([
254                    // DEPYLER-0170: Map to HashMap type, not HashMap::new
255                    // Constructor calls are handled separately in expr_gen.rs
256                    ("defaultdict".to_string(), "HashMap".to_string()),
257                    ("Counter".to_string(), "HashMap".to_string()),
258                    ("deque".to_string(), "VecDeque".to_string()),
259                    // DEPYLER-0936: Map OrderedDict to HashMap (not IndexMap which needs external crate)
260                    // HashMap in Rust 1.36+ preserves insertion order, so this is semantically correct
261                    ("OrderedDict".to_string(), "HashMap".to_string()),
262                ]),
263                // GH-204: Add constructor patterns to prevent E0423 errors
264                constructor_patterns: HashMap::from([
265                    ("defaultdict".to_string(), ConstructorPattern::New),
266                    ("Counter".to_string(), ConstructorPattern::New),
267                    ("deque".to_string(), ConstructorPattern::New),
268                    ("OrderedDict".to_string(), ConstructorPattern::New),
269                    ("VecDeque".to_string(), ConstructorPattern::New),
270                    ("HashMap".to_string(), ConstructorPattern::New),
271                    ("HashSet".to_string(), ConstructorPattern::New),
272                    ("BTreeMap".to_string(), ConstructorPattern::New),
273                    ("BTreeSet".to_string(), ConstructorPattern::New),
274                ]),
275            },
276        );
277
278        module_map.insert(
279            "math".to_string(),
280            ModuleMapping {
281                rust_path: "std::f64".to_string(),
282                is_external: false,
283                version: None,
284                item_map: HashMap::from([
285                    ("sqrt".to_string(), "sqrt".to_string()),
286                    ("sin".to_string(), "sin".to_string()),
287                    ("cos".to_string(), "cos".to_string()),
288                    ("tan".to_string(), "tan".to_string()),
289                    ("pi".to_string(), "consts::PI".to_string()),
290                    ("e".to_string(), "consts::E".to_string()),
291                    // DEPYLER-0771: isqrt is handled inline in expr_gen.rs
292                    ("isqrt".to_string(), "isqrt".to_string()),
293                ]),
294                constructor_patterns: HashMap::new(),
295            },
296        );
297
298        // DEPYLER-EXTDEPS-001: Enhanced random → rand mapping (Phase 2)
299        module_map.insert(
300            "random".to_string(),
301            ModuleMapping {
302                rust_path: "rand".to_string(),
303                is_external: true,
304                version: Some("0.8".to_string()),
305                item_map: HashMap::from([
306                    ("random".to_string(), "random".to_string()),
307                    ("randint".to_string(), "gen_range".to_string()),
308                    ("choice".to_string(), "choose".to_string()),
309                    ("shuffle".to_string(), "shuffle".to_string()),
310                    // Phase 2 additions
311                    ("uniform".to_string(), "gen_range".to_string()),
312                    ("seed".to_string(), "SeedableRng::seed_from_u64".to_string()),
313                    ("randrange".to_string(), "gen_range".to_string()),
314                    ("sample".to_string(), "choose_multiple".to_string()),
315                    ("gauss".to_string(), "Normal::sample".to_string()),
316                ]),
317                constructor_patterns: HashMap::new(),
318            },
319        );
320
321        module_map.insert(
322            "itertools".to_string(),
323            ModuleMapping {
324                rust_path: "itertools".to_string(),
325                is_external: true,
326                version: Some("0.11".to_string()),
327                item_map: HashMap::from([
328                    ("chain".to_string(), "chain".to_string()),
329                    ("combinations".to_string(), "combinations".to_string()),
330                    ("permutations".to_string(), "permutations".to_string()),
331                    ("product".to_string(), "iproduct".to_string()),
332                    // DEPYLER-0557: groupby uses Itertools trait method, not standalone function
333                    ("groupby".to_string(), "Itertools".to_string()),
334                    ("accumulate".to_string(), "scan".to_string()),
335                    ("takewhile".to_string(), "take_while".to_string()),
336                    ("dropwhile".to_string(), "drop_while".to_string()),
337                    ("cycle".to_string(), "cycle".to_string()),
338                    ("repeat".to_string(), "repeat_n".to_string()),
339                ]),
340                constructor_patterns: HashMap::new(),
341            },
342        );
343
344        module_map.insert(
345            "functools".to_string(),
346            ModuleMapping {
347                rust_path: "std".to_string(),
348                is_external: false,
349                version: None,
350                item_map: HashMap::from([
351                    ("reduce".to_string(), "".to_string()), // fold is a method on Iterator, no import needed
352                    ("partial".to_string(), "".to_string()), // Closures in Rust
353                    ("lru_cache".to_string(), "".to_string()), // Would need external crate
354                    ("wraps".to_string(), "".to_string()),  // Not applicable in Rust
355                ]),
356                constructor_patterns: HashMap::new(),
357            },
358        );
359
360        module_map.insert(
361            "hashlib".to_string(),
362            ModuleMapping {
363                rust_path: "sha2".to_string(),
364                is_external: true,
365                version: Some("0.10".to_string()),
366                item_map: HashMap::from([
367                    ("sha256".to_string(), "Sha256".to_string()),
368                    ("sha512".to_string(), "Sha512".to_string()),
369                    ("sha1".to_string(), "Sha1".to_string()),
370                    ("md5".to_string(), "Md5".to_string()),
371                ]),
372                constructor_patterns: HashMap::new(),
373            },
374        );
375
376        module_map.insert(
377            "base64".to_string(),
378            ModuleMapping {
379                rust_path: "base64".to_string(),
380                is_external: true,
381                version: Some("0.21".to_string()),
382                item_map: HashMap::from([
383                    ("b64encode".to_string(), "encode".to_string()),
384                    ("b64decode".to_string(), "decode".to_string()),
385                    ("urlsafe_b64encode".to_string(), "encode_config".to_string()),
386                    ("urlsafe_b64decode".to_string(), "decode_config".to_string()),
387                ]),
388                constructor_patterns: HashMap::new(),
389            },
390        );
391
392        module_map.insert(
393            "urllib.parse".to_string(),
394            ModuleMapping {
395                rust_path: "url".to_string(),
396                is_external: true,
397                version: Some("2.5".to_string()),
398                item_map: HashMap::from([
399                    ("urlparse".to_string(), "Url::parse".to_string()),
400                    ("urljoin".to_string(), "Url::join".to_string()),
401                    (
402                        "quote".to_string(),
403                        "percent_encoding::percent_encode".to_string(),
404                    ),
405                    (
406                        "unquote".to_string(),
407                        "percent_encoding::percent_decode".to_string(),
408                    ),
409                ]),
410                constructor_patterns: HashMap::new(),
411            },
412        );
413
414        module_map.insert(
415            "pathlib".to_string(),
416            ModuleMapping {
417                rust_path: "std::path".to_string(),
418                is_external: false,
419                version: None,
420                item_map: HashMap::from([
421                    ("Path".to_string(), "PathBuf".to_string()),
422                    ("PurePath".to_string(), "Path".to_string()),
423                ]),
424                // GH-204: Add constructor patterns for path types
425                constructor_patterns: HashMap::from([
426                    (
427                        "PathBuf".to_string(),
428                        ConstructorPattern::Method("from".to_string()),
429                    ),
430                    (
431                        "Path".to_string(),
432                        ConstructorPattern::Method("new".to_string()),
433                    ),
434                ]),
435            },
436        );
437
438        module_map.insert(
439            "tempfile".to_string(),
440            ModuleMapping {
441                rust_path: "tempfile".to_string(),
442                is_external: true,
443                version: Some("3.0".to_string()),
444                item_map: HashMap::from([
445                    (
446                        "NamedTemporaryFile".to_string(),
447                        "NamedTempFile".to_string(),
448                    ),
449                    ("TemporaryDirectory".to_string(), "TempDir".to_string()),
450                    ("mkstemp".to_string(), "tempfile".to_string()),
451                    ("mkdtemp".to_string(), "tempdir".to_string()),
452                ]),
453                // DEPYLER-0493: Specify constructor patterns for tempfile types
454                constructor_patterns: HashMap::from([
455                    // NamedTempFile is a struct → use ::new() pattern
456                    ("NamedTempFile".to_string(), ConstructorPattern::New),
457                    // TempDir is a struct → use ::new() pattern
458                    ("TempDir".to_string(), ConstructorPattern::New),
459                    // tempfile() is a function → call directly (no ::new)
460                    ("tempfile".to_string(), ConstructorPattern::Function),
461                    // tempdir() is a function → call directly (no ::new)
462                    ("tempdir".to_string(), ConstructorPattern::Function),
463                ]),
464            },
465        );
466
467        module_map.insert(
468            "csv".to_string(),
469            ModuleMapping {
470                rust_path: "csv".to_string(),
471                is_external: true,
472                version: Some("1.0".to_string()),
473                item_map: HashMap::from([
474                    ("reader".to_string(), "Reader".to_string()),
475                    ("writer".to_string(), "Writer".to_string()),
476                    ("DictReader".to_string(), "Reader".to_string()),
477                    ("DictWriter".to_string(), "Writer".to_string()),
478                ]),
479                constructor_patterns: HashMap::new(),
480            },
481        );
482
483        // =================================================================
484        // DEPYLER-EXTDEPS-001: Batuta Stack Mappings (Tier 0 - P0 Priority)
485        // =================================================================
486
487        // NumPy → Trueno (Spec Section 2.3)
488        // trueno provides SIMD/GPU-accelerated vector and matrix operations
489        module_map.insert(
490            "numpy".to_string(),
491            ModuleMapping {
492                rust_path: "trueno".to_string(),
493                is_external: true,
494                version: Some("0.7".to_string()),
495                item_map: HashMap::from([
496                    // Array creation
497                    ("array".to_string(), "Vector::from_slice".to_string()),
498                    ("zeros".to_string(), "Vector::zeros".to_string()),
499                    ("ones".to_string(), "Vector::ones".to_string()),
500                    ("empty".to_string(), "Vector::zeros".to_string()),
501                    ("arange".to_string(), "Vector::arange".to_string()),
502                    ("linspace".to_string(), "Vector::linspace".to_string()),
503                    // Element-wise operations
504                    ("add".to_string(), "Vector::add".to_string()),
505                    ("subtract".to_string(), "Vector::sub".to_string()),
506                    ("multiply".to_string(), "Vector::mul".to_string()),
507                    ("divide".to_string(), "Vector::div".to_string()),
508                    ("sqrt".to_string(), "Vector::sqrt".to_string()),
509                    ("exp".to_string(), "Vector::exp".to_string()),
510                    ("log".to_string(), "Vector::ln".to_string()),
511                    ("sin".to_string(), "Vector::sin".to_string()),
512                    ("cos".to_string(), "Vector::cos".to_string()),
513                    ("abs".to_string(), "Vector::abs".to_string()),
514                    // Dot product and matrix operations
515                    ("dot".to_string(), "Vector::dot".to_string()),
516                    ("matmul".to_string(), "Matrix::matmul".to_string()),
517                    // Reductions
518                    ("sum".to_string(), "Vector::sum".to_string()),
519                    ("mean".to_string(), "Vector::mean".to_string()),
520                    ("max".to_string(), "Vector::max".to_string()),
521                    ("min".to_string(), "Vector::min".to_string()),
522                    ("std".to_string(), "Vector::std".to_string()),
523                    ("var".to_string(), "Vector::var".to_string()),
524                    ("argmax".to_string(), "Vector::argmax".to_string()),
525                    ("argmin".to_string(), "Vector::argmin".to_string()),
526                    // Shape operations
527                    ("reshape".to_string(), "Matrix::reshape".to_string()),
528                    ("transpose".to_string(), "Matrix::transpose".to_string()),
529                    ("flatten".to_string(), "Vector::flatten".to_string()),
530                ]),
531                constructor_patterns: HashMap::new(),
532            },
533        );
534
535        // NumPy linalg submodule → Trueno linalg
536        module_map.insert(
537            "numpy.linalg".to_string(),
538            ModuleMapping {
539                rust_path: "trueno::linalg".to_string(),
540                is_external: true,
541                version: Some("0.7".to_string()),
542                item_map: HashMap::from([
543                    ("norm".to_string(), "norm".to_string()),
544                    ("inv".to_string(), "inv".to_string()),
545                    ("det".to_string(), "det".to_string()),
546                    ("eig".to_string(), "eig".to_string()),
547                    ("svd".to_string(), "svd".to_string()),
548                    ("solve".to_string(), "solve".to_string()),
549                ]),
550                constructor_patterns: HashMap::new(),
551            },
552        );
553
554        // Sklearn → Aprender (Spec Section 2.4)
555        // aprender provides ML algorithms compatible with sklearn API
556
557        // sklearn.linear_model → aprender::linear
558        module_map.insert(
559            "sklearn.linear_model".to_string(),
560            ModuleMapping {
561                rust_path: "aprender::linear".to_string(),
562                is_external: true,
563                version: Some("0.14".to_string()),
564                item_map: HashMap::from([
565                    (
566                        "LinearRegression".to_string(),
567                        "LinearRegression".to_string(),
568                    ),
569                    (
570                        "LogisticRegression".to_string(),
571                        "LogisticRegression".to_string(),
572                    ),
573                    ("Ridge".to_string(), "Ridge".to_string()),
574                    ("Lasso".to_string(), "Lasso".to_string()),
575                    ("ElasticNet".to_string(), "ElasticNet".to_string()),
576                ]),
577                constructor_patterns: HashMap::from([
578                    ("LinearRegression".to_string(), ConstructorPattern::New),
579                    ("LogisticRegression".to_string(), ConstructorPattern::New),
580                ]),
581            },
582        );
583
584        // sklearn.cluster → aprender::cluster
585        module_map.insert(
586            "sklearn.cluster".to_string(),
587            ModuleMapping {
588                rust_path: "aprender::cluster".to_string(),
589                is_external: true,
590                version: Some("0.14".to_string()),
591                item_map: HashMap::from([
592                    ("KMeans".to_string(), "KMeans".to_string()),
593                    ("DBSCAN".to_string(), "DBSCAN".to_string()),
594                    (
595                        "AgglomerativeClustering".to_string(),
596                        "Agglomerative".to_string(),
597                    ),
598                ]),
599                constructor_patterns: HashMap::from([(
600                    "KMeans".to_string(),
601                    ConstructorPattern::New,
602                )]),
603            },
604        );
605
606        // sklearn.tree → aprender::tree
607        module_map.insert(
608            "sklearn.tree".to_string(),
609            ModuleMapping {
610                rust_path: "aprender::tree".to_string(),
611                is_external: true,
612                version: Some("0.14".to_string()),
613                item_map: HashMap::from([
614                    (
615                        "DecisionTreeClassifier".to_string(),
616                        "DecisionTree".to_string(),
617                    ),
618                    (
619                        "DecisionTreeRegressor".to_string(),
620                        "DecisionTreeRegressor".to_string(),
621                    ),
622                ]),
623                constructor_patterns: HashMap::new(),
624            },
625        );
626
627        // sklearn.ensemble → aprender::ensemble
628        module_map.insert(
629            "sklearn.ensemble".to_string(),
630            ModuleMapping {
631                rust_path: "aprender::ensemble".to_string(),
632                is_external: true,
633                version: Some("0.14".to_string()),
634                item_map: HashMap::from([
635                    (
636                        "RandomForestClassifier".to_string(),
637                        "RandomForest".to_string(),
638                    ),
639                    (
640                        "RandomForestRegressor".to_string(),
641                        "RandomForestRegressor".to_string(),
642                    ),
643                    (
644                        "GradientBoostingClassifier".to_string(),
645                        "GradientBoosting".to_string(),
646                    ),
647                ]),
648                constructor_patterns: HashMap::new(),
649            },
650        );
651
652        // sklearn.preprocessing → aprender::preprocessing
653        module_map.insert(
654            "sklearn.preprocessing".to_string(),
655            ModuleMapping {
656                rust_path: "aprender::preprocessing".to_string(),
657                is_external: true,
658                version: Some("0.14".to_string()),
659                item_map: HashMap::from([
660                    ("StandardScaler".to_string(), "StandardScaler".to_string()),
661                    ("MinMaxScaler".to_string(), "MinMaxScaler".to_string()),
662                    ("LabelEncoder".to_string(), "LabelEncoder".to_string()),
663                    ("OneHotEncoder".to_string(), "OneHotEncoder".to_string()),
664                ]),
665                constructor_patterns: HashMap::new(),
666            },
667        );
668
669        // sklearn.decomposition → aprender::decomposition
670        module_map.insert(
671            "sklearn.decomposition".to_string(),
672            ModuleMapping {
673                rust_path: "aprender::decomposition".to_string(),
674                is_external: true,
675                version: Some("0.14".to_string()),
676                item_map: HashMap::from([
677                    ("PCA".to_string(), "PCA".to_string()),
678                    ("TruncatedSVD".to_string(), "TruncatedSVD".to_string()),
679                ]),
680                constructor_patterns: HashMap::new(),
681            },
682        );
683
684        // sklearn.model_selection → aprender::model_selection
685        module_map.insert(
686            "sklearn.model_selection".to_string(),
687            ModuleMapping {
688                rust_path: "aprender::model_selection".to_string(),
689                is_external: true,
690                version: Some("0.14".to_string()),
691                item_map: HashMap::from([
692                    (
693                        "train_test_split".to_string(),
694                        "train_test_split".to_string(),
695                    ),
696                    ("KFold".to_string(), "KFold".to_string()),
697                    ("cross_val_score".to_string(), "cross_val_score".to_string()),
698                    ("GridSearchCV".to_string(), "GridSearchCV".to_string()),
699                ]),
700                constructor_patterns: HashMap::new(),
701            },
702        );
703
704        // sklearn.metrics → aprender::metrics
705        module_map.insert(
706            "sklearn.metrics".to_string(),
707            ModuleMapping {
708                rust_path: "aprender::metrics".to_string(),
709                is_external: true,
710                version: Some("0.14".to_string()),
711                item_map: HashMap::from([
712                    ("accuracy_score".to_string(), "accuracy".to_string()),
713                    ("precision_score".to_string(), "precision".to_string()),
714                    ("recall_score".to_string(), "recall".to_string()),
715                    ("f1_score".to_string(), "f1".to_string()),
716                    (
717                        "confusion_matrix".to_string(),
718                        "confusion_matrix".to_string(),
719                    ),
720                    ("mean_squared_error".to_string(), "mse".to_string()),
721                    ("r2_score".to_string(), "r2".to_string()),
722                ]),
723                constructor_patterns: HashMap::new(),
724            },
725        );
726
727        // Pandas → Realizar (Spec Section 2.5)
728        // realizar provides DataFrame operations compatible with pandas API
729        module_map.insert(
730            "pandas".to_string(),
731            ModuleMapping {
732                rust_path: "realizar".to_string(),
733                is_external: true,
734                version: Some("0.4".to_string()),
735                item_map: HashMap::from([
736                    // Core types
737                    ("DataFrame".to_string(), "DataFrame".to_string()),
738                    ("Series".to_string(), "Series".to_string()),
739                    // I/O operations
740                    ("read_csv".to_string(), "read_csv".to_string()),
741                    ("read_json".to_string(), "read_json".to_string()),
742                    ("read_parquet".to_string(), "read_parquet".to_string()),
743                    ("to_csv".to_string(), "to_csv".to_string()),
744                    ("to_json".to_string(), "to_json".to_string()),
745                    // Selection and filtering
746                    ("concat".to_string(), "concat".to_string()),
747                    ("merge".to_string(), "join".to_string()),
748                    // Aggregation
749                    ("groupby".to_string(), "group_by".to_string()),
750                    ("agg".to_string(), "aggregate".to_string()),
751                    // Missing data
752                    ("isna".to_string(), "is_null".to_string()),
753                    ("fillna".to_string(), "fill_null".to_string()),
754                    ("dropna".to_string(), "drop_nulls".to_string()),
755                ]),
756                constructor_patterns: HashMap::from([
757                    ("DataFrame".to_string(), ConstructorPattern::New),
758                    ("Series".to_string(), ConstructorPattern::New),
759                ]),
760            },
761        );
762
763        // SciPy → Trueno (Spec Section 2.6)
764        // trueno::scipy provides scientific computing functions
765        module_map.insert(
766            "scipy".to_string(),
767            ModuleMapping {
768                rust_path: "trueno".to_string(),
769                is_external: true,
770                version: Some("0.7".to_string()),
771                item_map: HashMap::new(),
772                constructor_patterns: HashMap::new(),
773            },
774        );
775
776        // scipy.optimize → trueno::solver
777        module_map.insert(
778            "scipy.optimize".to_string(),
779            ModuleMapping {
780                rust_path: "trueno::optimize".to_string(),
781                is_external: true,
782                version: Some("0.7".to_string()),
783                item_map: HashMap::from([
784                    ("minimize".to_string(), "minimize".to_string()),
785                    ("minimize_scalar".to_string(), "minimize_scalar".to_string()),
786                    ("root".to_string(), "root".to_string()),
787                    ("brentq".to_string(), "brentq".to_string()),
788                    ("newton".to_string(), "newton".to_string()),
789                    ("curve_fit".to_string(), "curve_fit".to_string()),
790                ]),
791                constructor_patterns: HashMap::new(),
792            },
793        );
794
795        // scipy.stats → trueno::stats
796        module_map.insert(
797            "scipy.stats".to_string(),
798            ModuleMapping {
799                rust_path: "trueno::stats".to_string(),
800                is_external: true,
801                version: Some("0.7".to_string()),
802                item_map: HashMap::from([
803                    // Distributions
804                    ("norm".to_string(), "Normal".to_string()),
805                    ("uniform".to_string(), "Uniform".to_string()),
806                    ("expon".to_string(), "Exponential".to_string()),
807                    ("poisson".to_string(), "Poisson".to_string()),
808                    ("binom".to_string(), "Binomial".to_string()),
809                    // Statistical tests
810                    ("ttest_ind".to_string(), "ttest_ind".to_string()),
811                    ("ttest_1samp".to_string(), "ttest_1samp".to_string()),
812                    ("pearsonr".to_string(), "pearsonr".to_string()),
813                    ("spearmanr".to_string(), "spearmanr".to_string()),
814                    (
815                        "chi2_contingency".to_string(),
816                        "chi2_contingency".to_string(),
817                    ),
818                    ("kstest".to_string(), "kstest".to_string()),
819                ]),
820                constructor_patterns: HashMap::new(),
821            },
822        );
823
824        // scipy.interpolate → trueno::interpolate
825        module_map.insert(
826            "scipy.interpolate".to_string(),
827            ModuleMapping {
828                rust_path: "trueno::interpolate".to_string(),
829                is_external: true,
830                version: Some("0.7".to_string()),
831                item_map: HashMap::from([
832                    ("interp1d".to_string(), "Interp1d".to_string()),
833                    ("interp2d".to_string(), "Interp2d".to_string()),
834                    ("CubicSpline".to_string(), "CubicSpline".to_string()),
835                    (
836                        "UnivariateSpline".to_string(),
837                        "UnivariateSpline".to_string(),
838                    ),
839                ]),
840                constructor_patterns: HashMap::from([
841                    ("Interp1d".to_string(), ConstructorPattern::New),
842                    ("CubicSpline".to_string(), ConstructorPattern::New),
843                ]),
844            },
845        );
846
847        // scipy.signal → trueno::signal
848        module_map.insert(
849            "scipy.signal".to_string(),
850            ModuleMapping {
851                rust_path: "trueno::signal".to_string(),
852                is_external: true,
853                version: Some("0.7".to_string()),
854                item_map: HashMap::from([
855                    ("convolve".to_string(), "convolve".to_string()),
856                    ("correlate".to_string(), "correlate".to_string()),
857                    ("fft".to_string(), "fft".to_string()),
858                    ("ifft".to_string(), "ifft".to_string()),
859                    ("butter".to_string(), "butter".to_string()),
860                    ("filtfilt".to_string(), "filtfilt".to_string()),
861                ]),
862                constructor_patterns: HashMap::new(),
863            },
864        );
865
866        // =================================================================
867        // DEPYLER-EXTDEPS-001: High-Impact Standard Library Mappings (P0)
868        // =================================================================
869
870        // subprocess → std::process (146 occurrences in corpus)
871        module_map.insert(
872            "subprocess".to_string(),
873            ModuleMapping {
874                rust_path: "std::process".to_string(),
875                is_external: false,
876                version: None,
877                item_map: HashMap::from([
878                    // Process execution
879                    ("run".to_string(), "Command".to_string()),
880                    ("Popen".to_string(), "Command".to_string()),
881                    ("call".to_string(), "Command".to_string()),
882                    ("check_call".to_string(), "Command".to_string()),
883                    ("check_output".to_string(), "Command".to_string()),
884                    // I/O constants
885                    ("PIPE".to_string(), "Stdio::piped".to_string()),
886                    ("STDOUT".to_string(), "Stdio::inherit".to_string()),
887                    ("DEVNULL".to_string(), "Stdio::null".to_string()),
888                    // CompletedProcess fields map to Output
889                    ("CompletedProcess".to_string(), "Output".to_string()),
890                ]),
891                constructor_patterns: HashMap::from([(
892                    "Command".to_string(),
893                    ConstructorPattern::Method("new".to_string()),
894                )]),
895            },
896        );
897
898        // DEPYLER-0363: Map argparse to clap
899        // Note: This requires special handling in codegen for structural transformation
900        module_map.insert(
901            "argparse".to_string(),
902            ModuleMapping {
903                rust_path: "clap".to_string(),
904                is_external: true,
905                version: Some("4.5".to_string()),
906                item_map: HashMap::from([
907                    ("ArgumentParser".to_string(), "Parser".to_string()),
908                    // These require special codegen handling:
909                    // - ArgumentParser() → #[derive(Parser)] struct
910                    // - add_argument() → struct fields with #[arg] attributes
911                    // - parse_args() → Args::parse()
912                ]),
913                constructor_patterns: HashMap::new(),
914            },
915        );
916
917        // =================================================================
918        // DEPYLER-EXTDEPS-001: Phase 2 Mappings (P1 - Medium Impact)
919        // =================================================================
920
921        // threading → std::thread (stdlib mapping)
922        // Maps Python threading primitives to Rust std library equivalents
923        module_map.insert(
924            "threading".to_string(),
925            ModuleMapping {
926                rust_path: "std::thread".to_string(),
927                is_external: false,
928                version: None,
929                item_map: HashMap::from([
930                    // Thread creation and management
931                    ("Thread".to_string(), "spawn".to_string()),
932                    ("current_thread".to_string(), "current".to_string()),
933                    // Synchronization primitives (from std::sync)
934                    ("Lock".to_string(), "Mutex".to_string()),
935                    ("RLock".to_string(), "Mutex".to_string()),
936                    ("Event".to_string(), "Condvar".to_string()),
937                    ("Condition".to_string(), "Condvar".to_string()),
938                    ("Semaphore".to_string(), "Semaphore".to_string()),
939                    ("BoundedSemaphore".to_string(), "Semaphore".to_string()),
940                    ("Barrier".to_string(), "Barrier".to_string()),
941                ]),
942                constructor_patterns: HashMap::new(),
943            },
944        );
945
946        // asyncio → tokio (external async runtime)
947        // Maps Python asyncio primitives to Tokio equivalents
948        module_map.insert(
949            "asyncio".to_string(),
950            ModuleMapping {
951                rust_path: "tokio".to_string(),
952                is_external: true,
953                version: Some("1.35".to_string()),
954                item_map: HashMap::from([
955                    // Runtime and execution
956                    ("run".to_string(), "runtime::Runtime::block_on".to_string()),
957                    ("create_task".to_string(), "spawn".to_string()),
958                    // Time operations
959                    ("sleep".to_string(), "time::sleep".to_string()),
960                    ("wait_for".to_string(), "time::timeout".to_string()),
961                    // Concurrency primitives
962                    ("gather".to_string(), "join!".to_string()),
963                    ("wait".to_string(), "select!".to_string()),
964                    // Channel/Queue
965                    ("Queue".to_string(), "sync::mpsc::channel".to_string()),
966                    // Event loop (conceptually maps to runtime)
967                    (
968                        "get_event_loop".to_string(),
969                        "runtime::Handle::current".to_string(),
970                    ),
971                    (
972                        "new_event_loop".to_string(),
973                        "runtime::Runtime::new".to_string(),
974                    ),
975                ]),
976                constructor_patterns: HashMap::new(),
977            },
978        );
979
980        // struct → byteorder (binary data packing)
981        // Maps Python struct module to byteorder crate
982        module_map.insert(
983            "struct".to_string(),
984            ModuleMapping {
985                rust_path: "byteorder".to_string(),
986                is_external: true,
987                version: Some("1.5".to_string()),
988                item_map: HashMap::from([
989                    // Core pack/unpack operations
990                    ("pack".to_string(), "WriteBytesExt".to_string()),
991                    ("unpack".to_string(), "ReadBytesExt".to_string()),
992                    ("pack_into".to_string(), "WriteBytesExt".to_string()),
993                    ("unpack_from".to_string(), "ReadBytesExt".to_string()),
994                    // Size calculations
995                    ("calcsize".to_string(), "std::mem::size_of".to_string()),
996                    // Struct object for repeated use
997                    ("Struct".to_string(), "ByteOrder".to_string()),
998                ]),
999                constructor_patterns: HashMap::new(),
1000            },
1001        );
1002
1003        // statistics → statrs (statistical functions)
1004        // Maps Python statistics module to statrs crate
1005        module_map.insert(
1006            "statistics".to_string(),
1007            ModuleMapping {
1008                rust_path: "statrs".to_string(),
1009                is_external: true,
1010                version: Some("0.16".to_string()),
1011                item_map: HashMap::from([
1012                    // Central tendency
1013                    (
1014                        "mean".to_string(),
1015                        "statistics::Statistics::mean".to_string(),
1016                    ),
1017                    (
1018                        "median".to_string(),
1019                        "statistics::Statistics::median".to_string(),
1020                    ),
1021                    (
1022                        "mode".to_string(),
1023                        "statistics::Statistics::mode".to_string(),
1024                    ),
1025                    // Spread measures
1026                    (
1027                        "stdev".to_string(),
1028                        "statistics::Statistics::std_dev".to_string(),
1029                    ),
1030                    (
1031                        "variance".to_string(),
1032                        "statistics::Statistics::variance".to_string(),
1033                    ),
1034                    (
1035                        "pstdev".to_string(),
1036                        "statistics::Statistics::population_std_dev".to_string(),
1037                    ),
1038                    (
1039                        "pvariance".to_string(),
1040                        "statistics::Statistics::population_variance".to_string(),
1041                    ),
1042                    // Quantiles
1043                    (
1044                        "quantiles".to_string(),
1045                        "statistics::Statistics::percentile".to_string(),
1046                    ),
1047                ]),
1048                constructor_patterns: HashMap::new(),
1049            },
1050        );
1051
1052        // =================================================================
1053        // GH-204: E0425/E0433 Resolution - Additional stdlib mappings
1054        // =================================================================
1055
1056        // logging → log (standard Rust logging facade)
1057        // Maps Python logging module to log crate
1058        module_map.insert(
1059            "logging".to_string(),
1060            ModuleMapping {
1061                rust_path: "log".to_string(),
1062                is_external: true,
1063                version: Some("0.4".to_string()),
1064                item_map: HashMap::from([
1065                    // Log level functions
1066                    ("debug".to_string(), "debug!".to_string()),
1067                    ("info".to_string(), "info!".to_string()),
1068                    ("warning".to_string(), "warn!".to_string()),
1069                    ("warn".to_string(), "warn!".to_string()),
1070                    ("error".to_string(), "error!".to_string()),
1071                    ("critical".to_string(), "error!".to_string()),
1072                    // Configuration (often no-ops in Rust - use env_logger)
1073                    ("basicConfig".to_string(), "env_logger::init".to_string()),
1074                    ("getLogger".to_string(), "log::logger".to_string()),
1075                    // Log levels as constants
1076                    ("DEBUG".to_string(), "log::Level::Debug".to_string()),
1077                    ("INFO".to_string(), "log::Level::Info".to_string()),
1078                    ("WARNING".to_string(), "log::Level::Warn".to_string()),
1079                    ("ERROR".to_string(), "log::Level::Error".to_string()),
1080                    ("CRITICAL".to_string(), "log::Level::Error".to_string()),
1081                ]),
1082                constructor_patterns: HashMap::new(),
1083            },
1084        );
1085
1086        // configparser → config crate (INI file parsing)
1087        // Maps Python configparser module to config crate
1088        module_map.insert(
1089            "configparser".to_string(),
1090            ModuleMapping {
1091                rust_path: "config".to_string(),
1092                is_external: true,
1093                version: Some("0.14".to_string()),
1094                item_map: HashMap::from([
1095                    ("ConfigParser".to_string(), "Config".to_string()),
1096                    ("RawConfigParser".to_string(), "Config".to_string()),
1097                    ("SafeConfigParser".to_string(), "Config".to_string()),
1098                ]),
1099                constructor_patterns: HashMap::from([(
1100                    "Config".to_string(),
1101                    ConstructorPattern::Method("builder".to_string()),
1102                )]),
1103            },
1104        );
1105
1106        // unittest → Rust test module (no external crate needed)
1107        // Maps Python unittest to Rust's built-in test system
1108        module_map.insert(
1109            "unittest".to_string(),
1110            ModuleMapping {
1111                rust_path: "".to_string(), // Uses #[test] attribute
1112                is_external: false,
1113                version: None,
1114                item_map: HashMap::from([
1115                    // TestCase is handled via #[test] attribute
1116                    ("TestCase".to_string(), "".to_string()),
1117                    // Assertions map to assert! macros
1118                    ("assertEqual".to_string(), "assert_eq!".to_string()),
1119                    ("assertNotEqual".to_string(), "assert_ne!".to_string()),
1120                    ("assertTrue".to_string(), "assert!".to_string()),
1121                    ("assertFalse".to_string(), "assert!".to_string()),
1122                    ("assertIsNone".to_string(), "assert!".to_string()),
1123                    ("assertIsNotNone".to_string(), "assert!".to_string()),
1124                    ("assertIn".to_string(), "assert!".to_string()),
1125                    ("assertNotIn".to_string(), "assert!".to_string()),
1126                    ("assertRaises".to_string(), "assert!".to_string()),
1127                    ("main".to_string(), "".to_string()), // No-op, tests run via cargo test
1128                ]),
1129                constructor_patterns: HashMap::new(),
1130            },
1131        );
1132
1133        // traceback → backtrace crate (stack trace handling)
1134        // Maps Python traceback module to backtrace crate
1135        module_map.insert(
1136            "traceback".to_string(),
1137            ModuleMapping {
1138                rust_path: "backtrace".to_string(),
1139                is_external: true,
1140                version: Some("0.3".to_string()),
1141                item_map: HashMap::from([
1142                    ("print_exc".to_string(), "Backtrace::capture".to_string()),
1143                    ("format_exc".to_string(), "Backtrace::capture".to_string()),
1144                    ("print_tb".to_string(), "Backtrace::capture".to_string()),
1145                    ("format_tb".to_string(), "Backtrace::capture".to_string()),
1146                    ("extract_tb".to_string(), "Backtrace::capture".to_string()),
1147                ]),
1148                constructor_patterns: HashMap::new(),
1149            },
1150        );
1151
1152        // contextlib → No direct equivalent (handled inline)
1153        // Maps Python contextlib module - mostly handled via special codegen
1154        module_map.insert(
1155            "contextlib".to_string(),
1156            ModuleMapping {
1157                rust_path: "".to_string(), // Handled via Drop trait
1158                is_external: false,
1159                version: None,
1160                item_map: HashMap::from([
1161                    // These are largely handled by Rust's RAII pattern
1162                    ("contextmanager".to_string(), "".to_string()),
1163                    ("closing".to_string(), "".to_string()),
1164                    ("suppress".to_string(), "".to_string()),
1165                    ("nullcontext".to_string(), "".to_string()),
1166                ]),
1167                constructor_patterns: HashMap::new(),
1168            },
1169        );
1170
1171        Self { module_map }
1172    }
1173
1174    /// Map a Python import to Rust use statements
1175    ///
1176    /// # Examples
1177    ///
1178    /// ```rust
1179    /// use depyler_core::module_mapper::{ModuleMapper, RustImport};
1180    /// use depyler_core::hir::{Import, ImportItem};
1181    ///
1182    /// let mapper = ModuleMapper::new();
1183    /// let import = Import {
1184    ///     module: "json".to_string(),
1185    ///     items: vec![ImportItem::Named("loads".to_string())],
1186    /// };
1187    ///
1188    /// let rust_imports = mapper.map_import(&import);
1189    /// assert_eq!(rust_imports[0].path, "serde_json::from_str");
1190    /// assert!(rust_imports[0].is_external);
1191    /// ```
1192    pub fn map_import(&self, import: &Import) -> Vec<RustImport> {
1193        let mut rust_imports = Vec::new();
1194
1195        if let Some(mapping) = self.module_map.get(&import.module) {
1196            // If no specific items, it's a whole module import
1197            if import.items.is_empty() {
1198                // DEPYLER-0363: For mapped modules, emit the Rust equivalent
1199                // For argparse, this means `use clap::Parser;`
1200                if !mapping.rust_path.is_empty() {
1201                    // For external crates like argparse->clap, import the main trait/type
1202                    if import.module == "argparse" {
1203                        // ArgumentParser needs the Parser derive trait
1204                        rust_imports.push(RustImport {
1205                            path: format!("{}::Parser", mapping.rust_path),
1206                            alias: None,
1207                            is_external: mapping.is_external,
1208                        });
1209                    } else {
1210                        // For other modules, just import the module path
1211                        rust_imports.push(RustImport {
1212                            path: mapping.rust_path.clone(),
1213                            alias: Some(import.module.clone()),
1214                            is_external: mapping.is_external,
1215                        });
1216                    }
1217                } else {
1218                    // Empty rust_path means no direct mapping (like typing module)
1219                    rust_imports.push(RustImport {
1220                        path: format!("// Python import: {} (no Rust equivalent)", import.module),
1221                        alias: None,
1222                        is_external: false,
1223                    });
1224                }
1225            } else {
1226                // Handle each imported item
1227                for item in &import.items {
1228                    match item {
1229                        ImportItem::Named(name) => {
1230                            if let Some(rust_name) = mapping.item_map.get(name) {
1231                                rust_imports.push(RustImport {
1232                                    path: format!("{}::{}", mapping.rust_path, rust_name),
1233                                    alias: None,
1234                                    is_external: mapping.is_external,
1235                                });
1236                            } else {
1237                                // Direct mapping
1238                                rust_imports.push(RustImport {
1239                                    path: format!("{}::{}", mapping.rust_path, name),
1240                                    alias: None,
1241                                    is_external: mapping.is_external,
1242                                });
1243                            }
1244                        }
1245                        ImportItem::Aliased { name, alias } => {
1246                            if let Some(rust_name) = mapping.item_map.get(name) {
1247                                rust_imports.push(RustImport {
1248                                    path: format!("{}::{}", mapping.rust_path, rust_name),
1249                                    alias: Some(alias.clone()),
1250                                    is_external: mapping.is_external,
1251                                });
1252                            } else {
1253                                rust_imports.push(RustImport {
1254                                    path: format!("{}::{}", mapping.rust_path, name),
1255                                    alias: Some(alias.clone()),
1256                                    is_external: mapping.is_external,
1257                                });
1258                            }
1259                        }
1260                    }
1261                }
1262            }
1263        } else {
1264            // Unknown module - create a placeholder or warning
1265            rust_imports.push(RustImport {
1266                path: format!(
1267                    "// NOTE: Map Python module '{}' (tracked in DEPYLER-0424)",
1268                    import.module
1269                ),
1270                alias: None,
1271                is_external: false,
1272            });
1273        }
1274
1275        rust_imports
1276    }
1277
1278    /// Get all external dependencies needed
1279    ///
1280    /// # Examples
1281    ///
1282    /// ```rust
1283    /// use depyler_core::module_mapper::ModuleMapper;
1284    /// use depyler_core::hir::{Import, ImportItem};
1285    ///
1286    /// let mapper = ModuleMapper::new();
1287    /// let imports = vec![
1288    ///     Import {
1289    ///         module: "json".to_string(),
1290    ///         items: vec![ImportItem::Named("loads".to_string())],
1291    ///     },
1292    ///     Import {
1293    ///         module: "os".to_string(),
1294    ///         items: vec![ImportItem::Named("getcwd".to_string())],
1295    ///     },
1296    /// ];
1297    ///
1298    /// let deps = mapper.get_dependencies(&imports);
1299    /// assert_eq!(deps.len(), 1); // Only json is external
1300    /// assert_eq!(deps[0], ("serde_json".to_string(), "1.0".to_string()));
1301    /// ```
1302    pub fn get_dependencies(&self, imports: &[Import]) -> Vec<(String, String)> {
1303        let mut deps = Vec::new();
1304        let mut seen = std::collections::HashSet::new();
1305
1306        for import in imports {
1307            if let Some(mapping) = self.module_map.get(&import.module) {
1308                if mapping.is_external && !seen.contains(&mapping.rust_path) {
1309                    seen.insert(&mapping.rust_path);
1310                    if let Some(version) = &mapping.version {
1311                        deps.push((mapping.rust_path.clone(), version.clone()));
1312                    }
1313                }
1314            }
1315        }
1316
1317        deps
1318    }
1319
1320    /// Get module mapping for a given module name
1321    ///
1322    /// # Examples
1323    ///
1324    /// ```rust
1325    /// use depyler_core::module_mapper::ModuleMapper;
1326    ///
1327    /// let mapper = ModuleMapper::new();
1328    ///
1329    /// if let Some(mapping) = mapper.get_mapping("json") {
1330    ///     assert_eq!(mapping.rust_path, "serde_json");
1331    ///     assert!(mapping.is_external);
1332    ///     assert_eq!(mapping.version.as_ref().unwrap(), "1.0");
1333    /// }
1334    /// ```
1335    pub fn get_mapping(&self, module_name: &str) -> Option<&ModuleMapping> {
1336        self.module_map.get(module_name)
1337    }
1338}
1339
1340#[derive(Debug, Clone)]
1341pub struct RustImport {
1342    pub path: String,
1343    pub alias: Option<String>,
1344    pub is_external: bool,
1345}
1346
1347impl Default for ModuleMapper {
1348    fn default() -> Self {
1349        Self::new()
1350    }
1351}