use super::input::Input;
use super::source::{InputSource, ResolvedInputSource};
use crate::Result;
use crate::filter::PathExcludes;
use crate::types::file::FileExtensions;
use async_stream::try_stream;
use futures::stream::Stream;
use futures::stream::once;
use glob::glob_with;
use ignore::{Walk, WalkBuilder};
use shellexpand::tilde;
use std::path::Path;
use std::pin::Pin;
#[derive(Copy, Clone, Debug)]
pub struct InputResolver;
impl InputResolver {
#[must_use]
pub fn resolve<'a>(
input: &Input,
file_extensions: FileExtensions,
skip_hidden: bool,
skip_ignored: bool,
excluded_paths: &'a PathExcludes,
) -> Pin<Box<dyn Stream<Item = Result<ResolvedInputSource>> + Send + 'a>> {
Self::resolve_input(
input,
file_extensions,
skip_hidden,
skip_ignored,
excluded_paths,
)
}
pub(crate) fn walk(
path: &Path,
file_extensions: FileExtensions,
skip_hidden: bool,
skip_ignored: bool,
) -> Result<Walk> {
Ok(WalkBuilder::new(path)
.git_ignore(skip_ignored)
.git_global(skip_ignored)
.git_exclude(skip_ignored)
.ignore(skip_ignored)
.parents(skip_ignored)
.hidden(skip_hidden)
.types(file_extensions.build(skip_hidden)?)
.build())
}
fn resolve_input<'a>(
input: &Input,
file_extensions: FileExtensions,
skip_hidden: bool,
skip_ignored: bool,
excluded_paths: &'a PathExcludes,
) -> Pin<Box<dyn Stream<Item = Result<ResolvedInputSource>> + Send + 'a>> {
match &input.source {
InputSource::RemoteUrl(url) => {
let url = url.clone();
Box::pin(once(async move { Ok(ResolvedInputSource::RemoteUrl(url)) }))
}
InputSource::FsGlob {
pattern,
ignore_case,
} => {
let glob_expanded = tilde(pattern.as_str()).to_string();
let mut match_opts = glob::MatchOptions::new();
match_opts.case_sensitive = !ignore_case;
Box::pin(try_stream! {
for entry in glob_with(&glob_expanded, match_opts)? {
match entry {
Ok(path) => {
if path.is_dir() {
continue;
}
if Self::is_excluded_path(&path, excluded_paths) {
continue;
}
yield ResolvedInputSource::FsPath(path);
}
Err(e) => {
eprintln!("Error in glob pattern: {e:?}");
}
}
}
})
}
InputSource::FsPath(path) => {
if path.is_dir() {
let walk = match Self::walk(path, file_extensions, skip_hidden, skip_ignored) {
Ok(x) => x,
Err(e) => {
return Box::pin(once(async move { Err(e) }));
}
};
Box::pin(try_stream! {
for entry in walk {
let entry = entry?;
if Self::is_excluded_path(entry.path(), excluded_paths)
{
continue;
}
match entry.file_type() {
None => continue,
Some(file_type) => {
if !file_type.is_file() {
continue;
}
}
}
yield ResolvedInputSource::FsPath(
entry.path().to_path_buf()
);
}
})
} else {
if Self::is_excluded_path(path, excluded_paths) {
Box::pin(futures::stream::empty())
} else {
let path = path.clone();
Box::pin(once(async move { Ok(ResolvedInputSource::FsPath(path)) }))
}
}
}
InputSource::Stdin => Box::pin(once(async move { Ok(ResolvedInputSource::Stdin) })),
InputSource::String(s) => {
let s = s.clone();
Box::pin(once(async move { Ok(ResolvedInputSource::String(s)) }))
}
}
}
fn is_excluded_path(path: &Path, excluded_paths: &PathExcludes) -> bool {
excluded_paths.is_match(&path.to_string_lossy())
}
}