Skip to main content

py_canon/
frontend.rs

1//! The [`Frontend`] implementation for Python — kind declarations + the file-reading driver.
2//!
3//! The actual scan (single Ruff parse per file → [`Def`]s with canon precomputed off the AST
4//! nodes) lives in [`crate::defs::scan_source`]; this module owns the [`KindSpec`] vocabulary
5//! and the `Python` registry entry. Python declares five kinds; `interfaces` is TypeScript-only.
6
7use std::fs;
8use std::sync::Arc;
9
10use dup_defs_core::{Def, Frontend, KindSpec};
11use rayon::prelude::*;
12
13use crate::defs::scan_source;
14
15// The `KindSpec` vocabulary is shared across frontends — re-exported from `find-dup-defs-canon` so callers
16// (`crate::frontend::METHODS`, …) are unchanged. Python declares five kinds; `interfaces` is TS-only.
17pub use find_dup_defs_canon::kinds::{CLASSES, CONSTANTS, FUNCTIONS, METHODS, TYPE_ALIASES};
18
19static KINDS: &[&KindSpec] = &[&FUNCTIONS, &METHODS, &CLASSES, &CONSTANTS, &TYPE_ALIASES];
20
21/// Map the extraction's kind string to its `&'static KindSpec`. Internal to the frontend — the
22/// engine never does this; it reads `KindSpec` fields directly.
23pub(crate) fn kind_spec(id: &str) -> &'static KindSpec {
24    match id {
25        "functions" => &FUNCTIONS,
26        "methods" => &METHODS,
27        "classes" => &CLASSES,
28        "constants" => &CONSTANTS,
29        "type-aliases" => &TYPE_ALIASES,
30        other => unreachable!("py-canon emitted unknown kind {other:?}"),
31    }
32}
33
34/// Python frontend over Ruff's parser.
35pub struct Python;
36
37impl Frontend for Python {
38    fn lang(&self) -> &'static str {
39        "py"
40    }
41    fn extensions(&self) -> &'static [&'static str] {
42        &["py"]
43    }
44    fn kinds(&self) -> &'static [&'static KindSpec] {
45        KINDS
46    }
47    fn scan(&self, files: &[Arc<str>]) -> Vec<Def> {
48        files
49            .par_iter()
50            .flat_map(|f| fs::read_to_string(&**f).map_or_else(|_| Vec::new(), |src| scan_source(&src, f)))
51            .collect()
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use super::Python;
58    use dup_defs_core::Frontend;
59
60    #[test]
61    fn registry_metadata() {
62        let py = Python;
63        assert_eq!(py.lang(), "py");
64        assert_eq!(py.extensions(), &["py"]);
65        assert_eq!(py.kinds().len(), 5);
66        assert!(py.kinds().iter().all(|k| k.id != "interfaces"), "interfaces is TS-only");
67    }
68}