diff --git a/crates/brioche-core/src/project.rs b/crates/brioche-core/src/project.rs index 25a9351..391a8ff 100644 --- a/crates/brioche-core/src/project.rs +++ b/crates/brioche-core/src/project.rs @@ -212,6 +212,18 @@ impl Projects { Ok(module_paths.collect()) } + pub fn project_module_paths_for_projects( + &self, + project_hashes: &HashSet, + ) -> anyhow::Result> { + let projects = self + .inner + .read() + .map_err(|_| anyhow::anyhow!("failed to acquire 'projects' lock"))?; + let module_paths = projects.project_module_paths_for_projects(project_hashes)?; + Ok(module_paths.collect()) + } + pub fn project_module_specifiers( &self, project_hash: ProjectHash, @@ -233,7 +245,7 @@ impl Projects { .read() .map_err(|_| anyhow::anyhow!("failed to acquire 'projects' lock"))?; let module_specifiers = projects.project_module_specifiers_for_projects(project_hashes)?; - Ok(module_specifiers) + Ok(module_specifiers.collect()) } pub fn find_containing_project(&self, path: &Path) -> anyhow::Result> { @@ -548,6 +560,18 @@ impl ProjectsInner { Ok(super::script::specifier::BriocheModuleSpecifier::File { path }) } + pub fn project_module_paths_for_projects( + &self, + project_hashes: &HashSet, + ) -> anyhow::Result> { + let mut paths = Vec::with_capacity(project_hashes.len()); + for project_hash in project_hashes { + paths.push(self.project_module_paths(*project_hash)?); + } + + Ok(paths.into_iter().flatten()) + } + pub fn project_module_paths( &self, project_hash: ProjectHash, @@ -574,14 +598,15 @@ impl ProjectsInner { pub fn project_module_specifiers_for_projects( &self, project_hashes: &HashSet, - ) -> anyhow::Result> { - let mut module_specifiers = HashSet::new(); + ) -> anyhow::Result> + { + let mut module_specifiers = Vec::with_capacity(project_hashes.len()); for project_hash in project_hashes { let module_paths = self.project_module_specifiers(*project_hash)?; - module_specifiers.extend(module_paths); + module_specifiers.push(module_paths); } - Ok(module_specifiers) + Ok(module_specifiers.into_iter().flatten()) } pub fn project_module_specifiers( diff --git a/crates/brioche-core/src/script/format.rs b/crates/brioche-core/src/script/format.rs index 4c82387..1119909 100644 --- a/crates/brioche-core/src/script/format.rs +++ b/crates/brioche-core/src/script/format.rs @@ -1,40 +1,41 @@ +use std::collections::HashSet; use std::path::PathBuf; use crate::project::{ProjectHash, Projects}; -/// Formats the specified project using the provided formatter. +/// Formats the specified projects using the provided formatter. /// -/// This function takes a reference to the `Projects` struct, a `ProjectHash` representing the project to format. +/// This function takes a reference to the `Projects` struct, a list of `ProjectHash` representing the projects to format. /// It returns a `Result` containing a vector of `PathBuf` representing the paths of the formatted files, /// or an `anyhow::Error` if an error occurs. pub async fn format( projects: &Projects, - project_hash: ProjectHash, + project_hashes: &HashSet, ) -> anyhow::Result> { - format_project(projects, project_hash, false).await + format_project(projects, project_hashes, false).await } -/// Checks the formatting of the specified project using the provided formatter. +/// Checks the formatting of the specified projects using the provided formatter. /// -/// This function takes a reference to the `Projects` struct, a `ProjectHash` representing the project to check. +/// This function takes a reference to the `Projects` struct, a list of `ProjectHash` representing the projects to check. /// It returns a `Result` containing a vector of `PathBuf` representing the paths of the unformatted files, /// or an `anyhow::Error` if an error occurs. pub async fn check_format( projects: &Projects, - project_hash: ProjectHash, + project_hashes: &HashSet, ) -> anyhow::Result> { - format_project(projects, project_hash, true).await + format_project(projects, project_hashes, true).await } #[tracing::instrument(skip(projects), err)] async fn format_project( projects: &Projects, - project_hash: ProjectHash, + project_hashes: &HashSet, check: bool, ) -> anyhow::Result> { let mut result = vec![]; - let module_paths = projects.project_module_paths(project_hash)?; + let module_paths = projects.project_module_paths_for_projects(project_hashes)?; for path in module_paths { let contents = tokio::fs::read_to_string(&path).await?; diff --git a/crates/brioche/src/check.rs b/crates/brioche/src/check.rs index f346be2..7f68b21 100644 --- a/crates/brioche/src/check.rs +++ b/crates/brioche/src/check.rs @@ -61,8 +61,9 @@ pub async fn check( args.project.project }; - let mut project_names = HashMap::new(); - let mut projects_to_check = HashSet::new(); + // Pre-allocate capacity for project names and projects to check + let mut project_names = HashMap::with_capacity(project_paths.len()); + let mut projects_to_check = HashSet::with_capacity(project_paths.len()); // Load each path project for project_path in project_paths { diff --git a/crates/brioche/src/format.rs b/crates/brioche/src/format.rs index 6f413cd..ac8f9fb 100644 --- a/crates/brioche/src/format.rs +++ b/crates/brioche/src/format.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; +use std::collections::HashSet; use std::{path::PathBuf, process::ExitCode}; use brioche_core::{ @@ -38,36 +40,56 @@ pub async fn format(args: FormatArgs) -> anyhow::Result { let mut error_result = Option::None; - // Loop over the projects + // Pre-allocate capacity for project names and projects to format + let mut project_names = HashMap::with_capacity(args.project.len()); + let mut projects_to_format = HashSet::with_capacity(args.project.len()); + + // Load each path project for project_path in args.project { let project_name = format!("project '{name}'", name = project_path.display()); - - match projects + let project_hash = projects .load( &brioche, &project_path, ProjectValidation::Standard, ProjectLocking::Unlocked, ) - .await - { - Ok(project_hash) => { - let result = run_format( + .await; + + let project_hash = match project_hash { + Ok(project_hash) => project_hash, + Err(error) => { + consolidate_result( &reporter, - &projects, - project_hash, - &project_name, - args.check, - ) - .await; - consolidate_result(&reporter, Some(&project_name), result, &mut error_result); + Some(&project_name), + Err(error), + &mut error_result, + ); + continue; } - Err(e) => { - consolidate_result(&reporter, Some(&project_name), Err(e), &mut error_result); - } - } + }; + + project_names.entry(project_hash).or_insert(project_name); + projects_to_format.insert(project_hash); } + let project_name = if project_names.len() == 1 { + Some(project_names.values().next().unwrap()) + } else { + None + }; + let project_name = project_name.map(String::as_str); + + let result = run_format( + &reporter, + &projects, + &projects_to_format, + project_name, + args.check, + ) + .await; + consolidate_result(&reporter, project_name, result, &mut error_result); + guard.shutdown_console().await; brioche.wait_for_tasks().await; @@ -79,15 +101,15 @@ pub async fn format(args: FormatArgs) -> anyhow::Result { async fn run_format( reporter: &Reporter, projects: &Projects, - project_hash: ProjectHash, - project_name: &String, + project_hashes: &HashSet, + project_name: Option<&str>, check: bool, ) -> Result { let result = async { if check { - brioche_core::script::format::check_format(projects, project_hash).await + brioche_core::script::format::check_format(projects, project_hashes).await } else { - brioche_core::script::format::format(projects, project_hash).await + brioche_core::script::format::format(projects, project_hashes).await } } .instrument(tracing::info_span!("format")) @@ -100,37 +122,51 @@ async fn run_format( if !check { if !files.is_empty() { + let files = files + .iter() + .map(|file| format!("- {}", file.display())) + .collect::>() + .join("\n"); + + let format_string = project_name.map_or_else(|| format!("The following files in projects have been formatted:\n{files}"), |project_name| format!( + "The following files of {project_name} have been formatted:\n{files}" + )); + reporter.emit(superconsole::Lines::from_multiline_string( - &format!( - "The following files of {project_name} have been formatted:\n{files}", - files = files - .iter() - .map(|file| format!("- {}", file.display())) - .collect::>() - .join("\n") - ), + &format_string, superconsole::style::ContentStyle::default(), )); } Ok(true) } else if files.is_empty() { + let format_string = project_name.map_or_else( + || "All files in projects are formatted".to_string(), + |project_name| format!("All files of {project_name} are formatted"), + ); + reporter.emit(superconsole::Lines::from_multiline_string( - &format!("All files of {project_name} are formatted"), + &format_string, superconsole::style::ContentStyle::default(), )); Ok(true) } else { + let files = files + .iter() + .map(|file| format!("- {}", file.display())) + .collect::>() + .join("\n"); + + let format_string = project_name.map_or_else( + || format!("The following files in projects are not formatted:\n{files}"), + |project_name| { + format!("The following files of {project_name} are not formatted:\n{files}") + }, + ); + reporter.emit(superconsole::Lines::from_multiline_string( - &format!( - "The following files of {project_name} are not formatted:\n{files}", - files = files - .iter() - .map(|file| format!("- {}", file.display())) - .collect::>() - .join("\n") - ), + &format_string, superconsole::style::ContentStyle::default(), ));