1use std::{
2 collections::HashMap,
3 io::BufRead,
4 path::{Path, PathBuf},
5};
6
7use kondo_lib::{ProjectType, ScanOptions};
8use serde::Deserialize;
9
10pub struct UnityProject {
11 pub path: PathBuf,
12 pub name: String,
13 pub unity_version: String,
14 pub package_dependencies: HashMap<String, String>,
15}
16
17pub fn discover_projects(path: &Path) -> impl Iterator<Item = UnityProject> + '_ {
18 kondo_lib::scan(
19 &path,
20 &ScanOptions {
21 follow_symlinks: false,
22 same_file_system: false,
23 },
24 )
25 .filter_map(|entry| match entry {
26 Ok(project) if matches!(project.project_type, ProjectType::Unity) => Some(project),
27 _ => None,
28 })
29 .filter_map(|project| {
30 let name = project_name(&project.path).ok()??;
31
32 let unity_version = {
33 let fs = std::fs::File::open(project.path.join("ProjectSettings/ProjectVersion.txt"))
34 .ok()?;
35 let reader = std::io::BufReader::new(fs);
36 const UNITY_VERSION_PREFIX: &str = "m_EditorVersion: ";
37 reader.lines().find_map(|line| {
38 if let Ok(line) = line {
39 line.strip_prefix(UNITY_VERSION_PREFIX)
40 .map(ToOwned::to_owned)
41 } else {
42 None
43 }
44 })
45 }?;
46
47 let package_dependencies = {
48 #[derive(Deserialize)]
49 struct PackageManifest {
50 dependencies: HashMap<String, String>,
51 }
52
53 std::fs::read_to_string(project.path.join("Packages/manifest.json"))
54 .ok()
55 .and_then(|contents| serde_json::from_str::<PackageManifest>(&contents).ok())
56 .map(|manifest| manifest.dependencies)
57 }?;
58
59 Some(UnityProject {
60 name,
61 path: project.path,
62 unity_version,
63 package_dependencies,
64 })
65 })
66}
67
68pub fn project_name(path: &Path) -> Result<Option<String>, std::io::Error> {
69 let fs = std::fs::File::open(path.join("ProjectSettings/ProjectSettings.asset"))?;
70 let reader = std::io::BufReader::new(fs);
71 const PRODUCT_NAME_PREFIX: &str = " productName: ";
72
73 for line in reader.lines() {
74 if let Some(name) = line?.strip_prefix(PRODUCT_NAME_PREFIX) {
75 return Ok(Some(name.to_owned()));
76 }
77 }
78
79 Ok(None)
80}