use crate::diagnostics::{Diagnostic, Diagnostics, Error, Lint};
use crate::slice_file::SliceFile;
use crate::slice_options::SliceOptions;
use std::path::{Path, PathBuf};
use std::{fs, io};
#[derive(Debug, Eq)]
struct FilePath {
path: String,
canonicalized_path: PathBuf,
is_source: bool,
}
impl FilePath {
pub fn try_create(path: &str, is_source: bool) -> Result<Self, io::Error> {
PathBuf::from(path).canonicalize().map(|canonicalized_path| Self {
path: path.to_owned(),
canonicalized_path,
is_source,
})
}
}
impl PartialEq for FilePath {
fn eq(&self, other: &Self) -> bool {
self.canonicalized_path == other.canonicalized_path
}
}
fn remove_duplicate_file_paths(file_paths: Vec<FilePath>, diagnostics: &mut Diagnostics) -> Vec<FilePath> {
let mut deduped_file_paths = Vec::with_capacity(file_paths.len());
for file_path in file_paths {
if deduped_file_paths.contains(&file_path) {
let lint = Lint::DuplicateFile { path: file_path.path };
Diagnostic::new(lint).push_into(diagnostics);
} else {
deduped_file_paths.push(file_path);
}
}
deduped_file_paths
}
pub fn resolve_files_from(options: &SliceOptions, diagnostics: &mut Diagnostics) -> Vec<SliceFile> {
let mut file_paths = Vec::new();
let source_files = find_slice_files(&options.sources, true, diagnostics);
file_paths.extend(remove_duplicate_file_paths(source_files, diagnostics));
let reference_files = find_slice_files(&options.references, false, diagnostics);
for reference_file in remove_duplicate_file_paths(reference_files, diagnostics) {
if !file_paths.contains(&reference_file) {
file_paths.push(reference_file);
}
}
let mut files = Vec::new();
for file_path in file_paths {
match fs::read_to_string(&file_path.path) {
Ok(raw_text) => files.push(SliceFile::new(file_path.path, raw_text, file_path.is_source)),
Err(error) => Diagnostic::new(Error::IO {
action: "read",
path: file_path.path,
error,
})
.push_into(diagnostics),
}
}
files
}
fn find_slice_files(paths: &[String], are_source_files: bool, diagnostics: &mut Diagnostics) -> Vec<FilePath> {
let allow_directories = !are_source_files;
let mut slice_paths = Vec::new();
for path in paths {
let path_buf = PathBuf::from(path);
if !path_buf.exists() {
Diagnostic::new(Error::IO {
action: "read",
path: path.to_owned(),
error: io::ErrorKind::NotFound.into(),
})
.push_into(diagnostics);
continue;
}
if path_buf.is_file() && !is_slice_file(&path_buf) {
let io_error = io::Error::other("Slice files must end with a '.slice' extension");
Diagnostic::new(Error::IO {
action: "read",
path: path.to_owned(),
error: io_error,
})
.push_into(diagnostics);
continue;
}
if path_buf.is_dir() && !allow_directories {
let io_error = io::Error::other("Expected a Slice file but found a directory.");
Diagnostic::new(Error::IO {
action: "read",
path: path.to_owned(),
error: io_error,
})
.push_into(diagnostics);
continue;
}
slice_paths.extend(find_slice_files_in_path(path_buf, diagnostics));
}
slice_paths
.into_iter()
.map(|path| path.display().to_string())
.filter_map(|path| match FilePath::try_create(&path, are_source_files) {
Ok(file_path) => Some(file_path),
Err(error) => {
Diagnostic::new(Error::IO {
action: "read",
path,
error,
})
.push_into(diagnostics);
None
}
})
.collect()
}
fn find_slice_files_in_path(path: PathBuf, diagnostics: &mut Diagnostics) -> Vec<PathBuf> {
let mut paths = Vec::new();
if path.is_dir() {
match find_slice_files_in_directory(&path, diagnostics) {
Ok(child_paths) => paths.extend(child_paths),
Err(error) => Diagnostic::new(Error::IO {
action: "read",
path: path.display().to_string(),
error,
})
.push_into(diagnostics),
}
} else if path.is_file() && is_slice_file(&path) {
paths.push(path);
}
paths
}
fn find_slice_files_in_directory(path: &Path, diagnostics: &mut Diagnostics) -> io::Result<Vec<PathBuf>> {
let mut paths = Vec::new();
let dir = path.read_dir()?;
for child in dir {
match child {
Ok(child) => paths.extend(find_slice_files_in_path(child.path(), diagnostics)),
Err(error) => {
Diagnostic::new(Error::IO {
action: "read",
path: path.display().to_string(),
error,
})
.push_into(diagnostics);
continue;
}
}
}
Ok(paths)
}
fn is_slice_file(path: &Path) -> bool {
path.extension().filter(|ext| ext.to_str() == Some("slice")).is_some()
}