diff --git a/crates/core/src/common.rs b/crates/core/src/common.rs index 3a6d260..713458c 100644 --- a/crates/core/src/common.rs +++ b/crates/core/src/common.rs @@ -1,3 +1,5 @@ +use std::{borrow::Cow, collections::HashSet, path::PathBuf}; + /// Defines wrappers around types. /// /// For example, the macro invocation seen below: @@ -94,3 +96,77 @@ define_wrapper_type!( #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] CaseId => usize ); + +/// An iterator that finds files of a certain extension in the provided directory. You can think of +/// this a glob pattern similar to: `${path}/**/*.md` +pub struct FilesWithExtensionIterator { + /// The set of allowed extensions that that match the requirement and that should be returned + /// when found. + allowed_extensions: HashSet>, + + /// The set of directories to visit next. This iterator does BFS and so these directories will + /// only be visited if we can't find any files in our state. + directories_to_search: Vec, + + /// The set of files matching the allowed extensions that were found. If there are entries in + /// this vector then they will be returned when the [`Iterator::next`] method is called. If not + /// then we visit one of the next directories to visit. + /// + /// [`Iterator`]: std::iter::Iterator + files_matching_allowed_extensions: Vec, +} + +impl FilesWithExtensionIterator { + pub fn new(root_directory: PathBuf) -> Self { + Self { + allowed_extensions: Default::default(), + directories_to_search: vec![root_directory], + files_matching_allowed_extensions: Default::default(), + } + } + + pub fn with_allowed_extension( + mut self, + allowed_extension: impl Into>, + ) -> Self { + self.allowed_extensions.insert(allowed_extension.into()); + self + } +} + +impl Iterator for FilesWithExtensionIterator { + type Item = PathBuf; + + fn next(&mut self) -> Option { + if let Some(file_path) = self.files_matching_allowed_extensions.pop() { + return Some(file_path); + }; + + let directory_to_search = self.directories_to_search.pop()?; + + // Read all of the entries in the directory. If we failed to read this dir's entires then we + // elect to just ignore it and look in the next directory, we do that by calling the next + // method again on the iterator, which is an intentional decision that we made here instead + // of panicking. + let Ok(dir_entries) = std::fs::read_dir(directory_to_search) else { + return self.next(); + }; + + for entry in dir_entries.flatten() { + let entry_path = entry.path(); + if entry_path.is_dir() { + self.directories_to_search.push(entry_path) + } else if entry_path.is_file() + && entry_path.extension().is_some_and(|ext| { + self.allowed_extensions + .iter() + .any(|allowed| ext.eq_ignore_ascii_case(allowed.as_ref())) + }) + { + self.files_matching_allowed_extensions.push(entry_path) + } + } + + self.next() + } +} diff --git a/crates/core/src/driver/mod.rs b/crates/core/src/driver/mod.rs index d6783af..0603438 100644 --- a/crates/core/src/driver/mod.rs +++ b/crates/core/src/driver/mod.rs @@ -22,6 +22,7 @@ use std::collections::HashMap as StdHashMap; use tracing::Level; use crate::Platform; +use crate::common::*; type Contracts = HashMap< CompilerInput<<::Compiler as SolidityCompiler>::Options>, @@ -498,77 +499,3 @@ where Ok(()) } } - -/// An iterator that finds files of a certain extension in the provided directory. You can think of -/// this a glob pattern similar to: `${path}/**/*.md` -struct FilesWithExtensionIterator { - /// The set of allowed extensions that that match the requirement and that should be returned - /// when found. - allowed_extensions: std::collections::HashSet>, - - /// The set of directories to visit next. This iterator does BFS and so these directories will - /// only be visited if we can't find any files in our state. - directories_to_search: Vec, - - /// The set of files matching the allowed extensions that were found. If there are entries in - /// this vector then they will be returned when the [`Iterator::next`] method is called. If not - /// then we visit one of the next directories to visit. - /// - /// [`Iterator`]: std::iter::Iterator - files_matching_allowed_extensions: Vec, -} - -impl FilesWithExtensionIterator { - fn new(root_directory: std::path::PathBuf) -> Self { - Self { - allowed_extensions: Default::default(), - directories_to_search: vec![root_directory], - files_matching_allowed_extensions: Default::default(), - } - } - - fn with_allowed_extension( - mut self, - allowed_extension: impl Into>, - ) -> Self { - self.allowed_extensions.insert(allowed_extension.into()); - self - } -} - -impl Iterator for FilesWithExtensionIterator { - type Item = std::path::PathBuf; - - fn next(&mut self) -> Option { - if let Some(file_path) = self.files_matching_allowed_extensions.pop() { - return Some(file_path); - }; - - let directory_to_search = self.directories_to_search.pop()?; - - // Read all of the entries in the directory. If we failed to read this dir's entires then we - // elect to just ignore it and look in the next directory, we do that by calling the next - // method again on the iterator, which is an intentional decision that we made here instead - // of panicking. - let Ok(dir_entries) = std::fs::read_dir(directory_to_search) else { - return self.next(); - }; - - for entry in dir_entries.flatten() { - let entry_path = entry.path(); - if entry_path.is_dir() { - self.directories_to_search.push(entry_path) - } else if entry_path.is_file() - && entry_path.extension().is_some_and(|ext| { - self.allowed_extensions - .iter() - .any(|allowed| ext.eq_ignore_ascii_case(allowed.as_ref())) - }) - { - self.files_matching_allowed_extensions.push(entry_path) - } - } - - self.next() - } -}