use foundry_compilers_core::error::{SolcError, SolcIoError};
use serde::{Deserialize, Serialize};
use std::{
collections::BTreeMap,
fs,
path::{Path, PathBuf},
sync::Arc,
};
#[cfg(feature = "walkdir")]
use foundry_compilers_core::utils;
type SourcesInner = BTreeMap<PathBuf, Source>;
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Sources(pub SourcesInner);
impl Sources {
pub fn new() -> Self {
Self::default()
}
pub fn make_absolute(&mut self, root: &Path) {
self.0 = std::mem::take(&mut self.0)
.into_iter()
.map(|(path, source)| (root.join(path), source))
.collect();
}
pub fn all_dirty(&self) -> bool {
self.0.values().all(|s| s.is_dirty())
}
pub fn dirty(&self) -> impl Iterator<Item = (&PathBuf, &Source)> + '_ {
self.0.iter().filter(|(_, s)| s.is_dirty())
}
pub fn clean(&self) -> impl Iterator<Item = (&PathBuf, &Source)> + '_ {
self.0.iter().filter(|(_, s)| !s.is_dirty())
}
pub fn dirty_files(&self) -> impl Iterator<Item = &PathBuf> + '_ {
self.dirty().map(|(k, _)| k)
}
}
impl std::ops::Deref for Sources {
type Target = SourcesInner;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::DerefMut for Sources {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<I> From<I> for Sources
where
SourcesInner: From<I>,
{
fn from(value: I) -> Self {
Self(From::from(value))
}
}
impl<I> FromIterator<I> for Sources
where
SourcesInner: FromIterator<I>,
{
fn from_iter<T: IntoIterator<Item = I>>(iter: T) -> Self {
Self(FromIterator::from_iter(iter))
}
}
impl IntoIterator for Sources {
type Item = <SourcesInner as IntoIterator>::Item;
type IntoIter = <SourcesInner as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<'a> IntoIterator for &'a Sources {
type Item = <&'a SourcesInner as IntoIterator>::Item;
type IntoIter = <&'a SourcesInner as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
impl<'a> IntoIterator for &'a mut Sources {
type Item = <&'a mut SourcesInner as IntoIterator>::Item;
type IntoIter = <&'a mut SourcesInner as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.0.iter_mut()
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Source {
pub content: Arc<String>,
#[serde(skip, default)]
pub kind: SourceCompilationKind,
}
impl Source {
pub fn new(content: impl Into<String>) -> Self {
Self { content: Arc::new(content.into()), kind: SourceCompilationKind::Complete }
}
#[instrument(name = "Source::read", skip_all, err)]
pub fn read(file: &Path) -> Result<Self, SolcIoError> {
trace!(file=%file.display());
let mut content = fs::read_to_string(file).map_err(|err| SolcIoError::new(err, file))?;
if content.contains('\r') {
content = content.replace("\r\n", "\n");
}
Ok(Self::new(content))
}
pub fn read_(file: &Path) -> Result<Self, SolcError> {
Self::read(file).map_err(|err| {
let exists = err.path().exists();
if !exists && err.path().is_symlink() {
return SolcError::ResolveBadSymlink(err);
}
#[cfg(feature = "walkdir")]
if !exists
&& let Some(existing_file) =
foundry_compilers_core::utils::find_case_sensitive_existing_file(file)
{
return SolcError::ResolveCaseSensitiveFileName { error: err, existing_file };
}
SolcError::Resolve(err)
})
}
pub const fn is_dirty(&self) -> bool {
self.kind.is_dirty()
}
#[cfg(feature = "walkdir")]
pub fn read_all_from(dir: &Path, extensions: &[&str]) -> Result<Sources, SolcIoError> {
Self::read_all(utils::source_files_iter(dir, extensions))
}
#[cfg(feature = "walkdir")]
pub fn read_sol_yul_from(dir: &Path) -> Result<Sources, SolcIoError> {
Self::read_all_from(dir, utils::SOLC_EXTENSIONS)
}
pub fn read_all_files(files: Vec<PathBuf>) -> Result<Sources, SolcIoError> {
Self::read_all(files)
}
#[instrument(name = "Source::read_all", skip_all)]
pub fn read_all<T, I>(files: I) -> Result<Sources, SolcIoError>
where
I: IntoIterator<Item = T>,
T: Into<PathBuf>,
{
files
.into_iter()
.map(Into::into)
.map(|file| Self::read(&file).map(|source| (file, source)))
.collect()
}
#[cfg(feature = "rayon")]
pub fn par_read_all<T, I>(files: I) -> Result<Sources, SolcIoError>
where
I: IntoIterator<Item = T>,
<I as IntoIterator>::IntoIter: Send,
T: Into<PathBuf> + Send,
{
use rayon::{iter::ParallelBridge, prelude::ParallelIterator};
files
.into_iter()
.par_bridge()
.map(Into::into)
.map(|file| Self::read(&file).map(|source| (file, source)))
.collect::<Result<BTreeMap<_, _>, _>>()
.map(Sources)
}
#[cfg(feature = "checksum")]
pub fn content_hash(&self) -> String {
Self::content_hash_of(&self.content)
}
#[cfg(feature = "checksum")]
pub fn content_hash_of(src: &str) -> String {
foundry_compilers_core::utils::unique_hash(src)
}
}
#[cfg(feature = "async")]
impl Source {
#[instrument(name = "Source::async_read", skip_all, err)]
pub async fn async_read(file: &Path) -> Result<Self, SolcIoError> {
let mut content =
tokio::fs::read_to_string(file).await.map_err(|err| SolcIoError::new(err, file))?;
if content.contains('\r') {
content = content.replace("\r\n", "\n");
}
Ok(Self::new(content))
}
#[cfg(feature = "walkdir")]
pub async fn async_read_all_from(
dir: &Path,
extensions: &[&str],
) -> Result<Sources, SolcIoError> {
Self::async_read_all(utils::source_files(dir, extensions)).await
}
pub async fn async_read_all<T, I>(files: I) -> Result<Sources, SolcIoError>
where
I: IntoIterator<Item = T>,
T: Into<PathBuf>,
{
futures_util::future::join_all(
files
.into_iter()
.map(Into::into)
.map(|file| async { Self::async_read(&file).await.map(|source| (file, source)) }),
)
.await
.into_iter()
.collect()
}
}
impl AsRef<str> for Source {
fn as_ref(&self) -> &str {
&self.content
}
}
impl AsRef<[u8]> for Source {
fn as_ref(&self) -> &[u8] {
self.content.as_bytes()
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum SourceCompilationKind {
#[default]
Complete,
Optimized,
}
impl SourceCompilationKind {
pub const fn is_dirty(&self) -> bool {
matches!(self, Self::Complete)
}
}