suitcase/internal/utils/
dart.rs

1use crate::{exec_on, internal::shell::Shell};
2use anyhow::Context;
3use log::debug;
4use std::{env, path::PathBuf};
5
6const IGNORED_FOLDERS: [&str; 12] = [
7    "ios",
8    "android",
9    "windows",
10    "linux",
11    "macos",
12    ".symlinks",
13    ".plugin_symlinks",
14    ".dart_tool",
15    "build",
16    ".fvm",
17    "flutter_sdk",
18    "flutter_gen",
19];
20
21pub struct DartShell<'a> {
22    shell: &'a Shell,
23}
24
25impl<'a> DartShell<'a> {
26    pub fn new(shell: &'a Shell) -> Self {
27        Self { shell }
28    }
29
30    pub fn find_dart_projects(
31        &self,
32        path: Option<&PathBuf>,
33    ) -> anyhow::Result<Vec<DartProjectMetadata>> {
34        let cwd = env::current_dir().context("trying to get current directory")?;
35        let path = path.unwrap_or(&cwd);
36        debug!("finding Dart projects recursively in path: {:?}", path);
37
38        let path_str = path.to_str().context(format!(
39            "trying to convert path '{}' to a string",
40            path.display()
41        ))?;
42
43        let output = exec_on!(
44            self.shell,
45            "find",
46            path_str,
47            "-type",
48            "f",
49            "-name",
50            "pubspec.yaml",
51            "-exec",
52            "dirname",
53            "{}",
54            ";"
55        )
56        .context(format!(
57            "trying to find Dart projects in path '{}'",
58            path.display()
59        ))?;
60
61        let projects = output
62            .stdout
63            .lines()
64            .map(|line| PathBuf::try_from(line.trim()))
65            .filter_map(Result::ok)
66            .map(|path| path.canonicalize())
67            .filter_map(Result::ok)
68            .filter(|path| {
69                let Some(last_component) = path.components().last() else {
70                    return true;
71                };
72
73                let Some(dir_name) = last_component.as_os_str().to_str() else {
74                    return true;
75                };
76
77                !IGNORED_FOLDERS.contains(&dir_name)
78            })
79            .map(|path| self.get_dart_project_metadata(path))
80            .collect::<Result<Vec<_>, _>>()?;
81
82        debug!("found {} Dart projects", projects.len());
83
84        Ok(projects)
85    }
86
87    pub fn get_dart_project_metadata(&self, path: PathBuf) -> anyhow::Result<DartProjectMetadata> {
88        let name = path
89            .file_name()
90            .context(format!(
91                "trying to get file name from path '{}'",
92                path.display()
93            ))?
94            .to_str()
95            .context(format!(
96                "trying to convert file name from path '{}' to a string",
97                path.display()
98            ))?
99            .to_string();
100
101        let pubspec_file_path = path.join("pubspec.yaml");
102
103        let pubspec_file = std::fs::File::open(&pubspec_file_path).context(format!(
104            "trying to open pubspec.yaml file at path '{}'",
105            pubspec_file_path.display()
106        ))?;
107        let pubspec_yaml: serde_yaml::Value =
108            serde_yaml::from_reader(pubspec_file).context(format!(
109                "trying to parse pubspec.yaml file at path '{}'",
110                pubspec_file_path.display()
111            ))?;
112
113        let is_flutter_project = !pubspec_yaml["dependencies"]["flutter"].is_null();
114
115        Ok(DartProjectMetadata {
116            path,
117            name,
118            is_flutter_project,
119        })
120    }
121}
122
123#[derive(Debug)]
124pub struct DartProjectMetadata {
125    pub path: PathBuf,
126    pub name: String,
127    pub is_flutter_project: bool,
128}