unity 0.2.0

Library for working with Unity projects
Documentation
use std::{
    collections::HashMap,
    io::BufRead,
    path::{Path, PathBuf},
};

use kondo_lib::{ProjectType, ScanOptions};
use serde::Deserialize;

pub struct UnityProject {
    pub path: PathBuf,
    pub name: String,
    pub unity_version: String,
    pub package_dependencies: HashMap<String, String>,
}

pub fn discover_projects(path: &Path) -> impl Iterator<Item = UnityProject> + '_ {
    kondo_lib::scan(
        &path,
        &ScanOptions {
            follow_symlinks: false,
            same_file_system: false,
        },
    )
    .filter_map(|entry| match entry {
        Ok(project) if matches!(project.project_type, ProjectType::Unity) => Some(project),
        _ => None,
    })
    .filter_map(|project| {
        let name = project_name(&project.path).ok()??;

        let unity_version = {
            let fs = std::fs::File::open(project.path.join("ProjectSettings/ProjectVersion.txt"))
                .ok()?;
            let reader = std::io::BufReader::new(fs);
            const UNITY_VERSION_PREFIX: &str = "m_EditorVersion: ";
            reader.lines().find_map(|line| {
                if let Ok(line) = line {
                    line.strip_prefix(UNITY_VERSION_PREFIX)
                        .map(ToOwned::to_owned)
                } else {
                    None
                }
            })
        }?;

        let package_dependencies = {
            #[derive(Deserialize)]
            struct PackageManifest {
                dependencies: HashMap<String, String>,
            }

            std::fs::read_to_string(project.path.join("Packages/manifest.json"))
                .ok()
                .and_then(|contents| serde_json::from_str::<PackageManifest>(&contents).ok())
                .map(|manifest| manifest.dependencies)
        }?;

        Some(UnityProject {
            name,
            path: project.path,
            unity_version,
            package_dependencies,
        })
    })
}

pub fn project_name(path: &Path) -> Result<Option<String>, std::io::Error> {
    let fs = std::fs::File::open(path.join("ProjectSettings/ProjectSettings.asset"))?;
    let reader = std::io::BufReader::new(fs);
    const PRODUCT_NAME_PREFIX: &str = "  productName: ";

    for line in reader.lines() {
        if let Some(name) = line?.strip_prefix(PRODUCT_NAME_PREFIX) {
            return Ok(Some(name.to_owned()));
        }
    }

    Ok(None)
}