changepacks_python/
finder.rs1use anyhow::{Context, Result};
2use async_trait::async_trait;
3use changepacks_core::{Project, ProjectFinder};
4use std::{
5 collections::HashMap,
6 path::{Path, PathBuf},
7};
8use tokio::fs::read_to_string;
9
10use crate::{package::PythonPackage, workspace::PythonWorkspace};
11
12#[derive(Debug)]
13pub struct PythonProjectFinder {
14 projects: HashMap<PathBuf, Project>,
15 project_files: Vec<&'static str>,
16}
17
18impl Default for PythonProjectFinder {
19 fn default() -> Self {
20 Self::new()
21 }
22}
23
24impl PythonProjectFinder {
25 pub fn new() -> Self {
26 Self {
27 projects: HashMap::new(),
28 project_files: vec!["pyproject.toml"],
29 }
30 }
31}
32
33#[async_trait]
34impl ProjectFinder for PythonProjectFinder {
35 fn projects(&self) -> Vec<&Project> {
36 self.projects.values().collect::<Vec<_>>()
37 }
38 fn projects_mut(&mut self) -> Vec<&mut Project> {
39 self.projects.values_mut().collect::<Vec<_>>()
40 }
41
42 fn project_files(&self) -> &[&str] {
43 &self.project_files
44 }
45
46 async fn visit(&mut self, path: &Path, relative_path: &Path) -> Result<()> {
47 if path.is_file()
48 && self.project_files().contains(
49 &path
50 .file_name()
51 .context(format!("File name not found - {}", path.display()))?
52 .to_str()
53 .context(format!("File name not found - {}", path.display()))?,
54 )
55 {
56 if self.projects.contains_key(path) {
57 return Ok(());
58 }
59 let pyproject_toml = read_to_string(path).await?;
61 let pyproject_toml: toml::Value = toml::from_str(&pyproject_toml)?;
62 let project = pyproject_toml
63 .get("project")
64 .context(format!("Project not found - {}", path.display()))?;
65
66 if pyproject_toml
68 .get("tool")
69 .and_then(|t| t.get("uv").and_then(|u| u.get("workspace")))
70 .is_some()
71 {
72 let version = project["version"].as_str().map(|v| v.to_string());
73 let name = project["name"].as_str().map(|v| v.to_string());
74 self.projects.insert(
75 path.to_path_buf(),
76 Project::Workspace(Box::new(PythonWorkspace::new(
77 name,
78 version,
79 path.to_path_buf(),
80 relative_path.to_path_buf(),
81 ))),
82 );
83 } else {
84 let version = project
85 .get("version")
86 .and_then(|v| v.as_str())
87 .map(|v| v.to_string())
88 .context(format!("Version not found - {}", path.display()))?;
89 let name = project
90 .get("name")
91 .and_then(|v| v.as_str())
92 .map(|v| v.to_string())
93 .context(format!("Name not found - {}", path.display()))?;
94 self.projects.insert(
95 path.to_path_buf(),
96 Project::Package(Box::new(PythonPackage::new(
97 name,
98 version,
99 path.to_path_buf(),
100 relative_path.to_path_buf(),
101 ))),
102 );
103 }
104 }
105 Ok(())
106 }
107}