cruxlines 0.1.0

Ranks symbol definitions by cross-file references using tree-sitter.
Documentation
use std::collections::HashSet;
use std::path::PathBuf;

use ignore::WalkBuilder;

#[derive(Debug)]
pub enum CliIoError {
    CurrentDir(std::io::Error),
    ReadFile { path: PathBuf, source: std::io::Error },
}

pub fn gather_inputs<I>(paths: I) -> Result<Vec<(PathBuf, String)>, CliIoError>
where
    I: IntoIterator<Item = PathBuf>,
{
    let cwd = std::env::current_dir().map_err(CliIoError::CurrentDir)?;
    let mut requested_files: HashSet<PathBuf> = HashSet::new();
    let mut requested_dirs: HashSet<PathBuf> = HashSet::new();
    for path in paths {
        let abs = if path.is_absolute() {
            path
        } else {
            cwd.join(path)
        };
        if abs.is_dir() {
            requested_dirs.insert(abs);
        } else {
            requested_files.insert(abs);
        }
    }

    let mut inputs = Vec::new();
    for file in requested_files {
        if !file.is_file() {
            continue;
        }
        if !cruxlines::is_supported_path(&file) {
            continue;
        }
        let bytes = std::fs::read(&file)
            .map_err(|source| CliIoError::ReadFile {
                path: file.clone(),
                source,
            })?;
        let contents = match String::from_utf8(bytes) {
            Ok(contents) => contents,
            Err(_) => {
                continue;
            }
        };
        inputs.push((file, contents));
    }

    if !requested_dirs.is_empty() {
        let mut roots: Vec<PathBuf> = requested_dirs.into_iter().collect();
        roots.sort();
        roots.dedup();

        let mut builder = WalkBuilder::new(&roots[0]);
        for path in &roots[1..] {
            builder.add(path);
        }
        for entry in builder.build() {
            let entry = match entry {
                Ok(entry) => entry,
                Err(_) => continue,
            };
            if !entry
                .file_type()
                .map(|file_type| file_type.is_file())
                .unwrap_or(false)
            {
                continue;
            }
            let path = entry.path();
            if !cruxlines::is_supported_path(path) {
                continue;
            }
            let bytes = std::fs::read(path)
                .map_err(|source| CliIoError::ReadFile {
                    path: path.to_path_buf(),
                    source,
                })?;
            let contents = match String::from_utf8(bytes) {
                Ok(contents) => contents,
                Err(_) => {
                    continue;
                }
            };
            inputs.push((path.to_path_buf(), contents));
        }
    }

    Ok(inputs)
}