#![warn(unused_extern_crates)]
#![allow(clippy::missing_errors_doc)]
use std::fs;
use api::Solution;
use color_eyre::{eyre::Context, Result};
use jwalk::{Parallelism, WalkDir};
pub mod api;
mod ast;
mod lex;
pub mod msbuild;
mod parser;
#[macro_use]
extern crate lalrpop_util;
#[cfg(test)] extern crate rstest;
lalrpop_mod!(
#[allow(clippy::all)]
#[allow(unused)]
solp
);
pub trait Consume {
fn ok(&mut self, solution: &Solution);
fn err(&self, path: &str);
}
pub fn parse_file(path: &str, consumer: &mut dyn Consume) -> Result<()> {
let contents = fs::read_to_string(path).wrap_err_with(|| {
consumer.err(path);
format!("Failed to read content from path: {path}")
})?;
let mut solution = parse_str(&contents).wrap_err_with(|| {
consumer.err(path);
format!("Failed to parse solution from path: {path}")
})?;
solution.path = path;
consumer.ok(&solution);
Ok(())
}
pub fn parse_str(contents: &str) -> Result<Solution> {
let parsed = parser::parse_str(contents)?;
Ok(Solution::from(&parsed))
}
pub fn parse_dir(path: &str, extension: &str, consumer: &mut dyn Consume) -> usize {
let iter = create_dir_iterator(path).max_depth(1);
parse_dir_or_tree(iter, extension, consumer)
}
pub fn parse_dir_tree(path: &str, extension: &str, consumer: &mut dyn Consume) -> usize {
let parallelism = Parallelism::RayonNewPool(num_cpus::get_physical());
let iter = create_dir_iterator(path).parallelism(parallelism);
parse_dir_or_tree(iter, extension, consumer)
}
fn create_dir_iterator(path: &str) -> WalkDir {
let root = decorate_path(path);
WalkDir::new(root).skip_hidden(false).follow_links(false)
}
fn parse_dir_or_tree(iter: WalkDir, extension: &str, consumer: &mut dyn Consume) -> usize {
let ext = extension.trim_start_matches('.');
iter.into_iter()
.filter_map(std::result::Result::ok)
.filter(|f| f.file_type().is_file())
.map(|f| f.path())
.filter(|p| p.extension().map(|s| s == ext).unwrap_or_default())
.map(|f| f.to_str().unwrap_or("").to_string())
.filter_map(|fp| parse_file(&fp, consumer).ok())
.count()
}
#[cfg(target_os = "windows")]
fn decorate_path(path: &str) -> String {
if path.len() == 2 && path.ends_with(':') {
format!("{path}\\")
} else {
path.to_owned()
}
}
#[cfg(not(target_os = "windows"))]
fn decorate_path(path: &str) -> String {
path.to_owned()
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
#[cfg(not(target_os = "windows"))]
#[rstest]
#[case("", "")]
#[case("/", "/")]
#[case("/home", "/home")]
#[case("d:", "d:")]
#[trace]
fn decorate_path_tests(#[case] raw_path: &str, #[case] expected: &str) {
let actual = decorate_path(raw_path);
assert_eq!(actual, expected);
}
#[cfg(target_os = "windows")]
#[rstest]
#[case("", "")]
#[case("/", "/")]
#[case("d:", "d:\\")]
#[case("dd:", "dd:")]
#[trace]
fn decorate_path_tests(#[case] raw_path: &str, #[case] expected: &str) {
let actual = decorate_path(raw_path);
assert_eq!(actual, expected);
}
}