suitcase 0.1.4

A set of personal CLI tools to automate common tasks in software development (including Rust, Dart, and Flutter), written in Rust.
Documentation
use crate::{exec_on, internal::shell::Shell};
use anyhow::Context;
use log::debug;
use std::{env, path::PathBuf};

const IGNORED_FOLDERS: [&str; 12] = [
    "ios",
    "android",
    "windows",
    "linux",
    "macos",
    ".symlinks",
    ".plugin_symlinks",
    ".dart_tool",
    "build",
    ".fvm",
    "flutter_sdk",
    "flutter_gen",
];

pub struct DartShell<'a> {
    shell: &'a Shell,
}

impl<'a> DartShell<'a> {
    pub fn new(shell: &'a Shell) -> Self {
        Self { shell }
    }

    pub fn find_dart_projects(
        &self,
        path: Option<&PathBuf>,
    ) -> anyhow::Result<Vec<DartProjectMetadata>> {
        let cwd = env::current_dir().context("trying to get current directory")?;
        let path = path.unwrap_or(&cwd);
        debug!("finding Dart projects recursively in path: {:?}", path);

        let path_str = path.to_str().context(format!(
            "trying to convert path '{}' to a string",
            path.display()
        ))?;

        let output = exec_on!(
            self.shell,
            "find",
            path_str,
            "-type",
            "f",
            "-name",
            "pubspec.yaml",
            "-exec",
            "dirname",
            "{}",
            ";"
        )
        .context(format!(
            "trying to find Dart projects in path '{}'",
            path.display()
        ))?;

        let projects = output
            .stdout
            .lines()
            .map(|line| PathBuf::try_from(line.trim()))
            .filter_map(Result::ok)
            .map(|path| path.canonicalize())
            .filter_map(Result::ok)
            .filter(|path| {
                let Some(last_component) = path.components().last() else {
                    return true;
                };

                let Some(dir_name) = last_component.as_os_str().to_str() else {
                    return true;
                };

                !IGNORED_FOLDERS.contains(&dir_name)
            })
            .map(|path| self.get_dart_project_metadata(path))
            .collect::<Result<Vec<_>, _>>()?;

        debug!("found {} Dart projects", projects.len());

        Ok(projects)
    }

    pub fn get_dart_project_metadata(&self, path: PathBuf) -> anyhow::Result<DartProjectMetadata> {
        let name = path
            .file_name()
            .context(format!(
                "trying to get file name from path '{}'",
                path.display()
            ))?
            .to_str()
            .context(format!(
                "trying to convert file name from path '{}' to a string",
                path.display()
            ))?
            .to_string();

        let pubspec_file_path = path.join("pubspec.yaml");

        let pubspec_file = std::fs::File::open(&pubspec_file_path).context(format!(
            "trying to open pubspec.yaml file at path '{}'",
            pubspec_file_path.display()
        ))?;
        let pubspec_yaml: serde_yaml::Value =
            serde_yaml::from_reader(pubspec_file).context(format!(
                "trying to parse pubspec.yaml file at path '{}'",
                pubspec_file_path.display()
            ))?;

        let is_flutter_project = !pubspec_yaml["dependencies"]["flutter"].is_null();

        Ok(DartProjectMetadata {
            path,
            name,
            is_flutter_project,
        })
    }
}

#[derive(Debug)]
pub struct DartProjectMetadata {
    pub path: PathBuf,
    pub name: String,
    pub is_flutter_project: bool,
}