use anyhow::{anyhow, Result};
use loc::Count;
use regex::Regex;
use std::{
collections::HashMap,
fs::read_to_string,
path::{ PathBuf},
};
use thiserror::Error;
use walkdir::{ WalkDir};
use super::code;
use super::detector;
use super::ruleset;
#[derive(Error, Debug)]
pub enum ProjectError {
#[error("Directory {0} Cannot be found!")]
NotFound(String),
}
#[derive(Debug)]
pub struct Project {
pub dir: PathBuf,
pub project_langs: Option<Vec<String>>,
pub is_git: Option<bool>,
pub generic_gitignore: Option<Vec<String>>,
pub gitignore_ruleset: Option<ruleset::RuleSet>,
pub code_stats: Option<HashMap<String, loc::Count>>,
}
#[derive(Debug)]
pub struct IsIgnored {
exists: bool,
is_dir: bool,
is_ignored: bool,
}
impl Project {
pub fn new(dir_path: &str) -> Result<Project> {
let dir_path = PathBuf::from(dir_path);
if !dir_path.exists() {
return Err(anyhow!(ProjectError::NotFound(
dir_path.to_string_lossy().to_string()
)));
}
let mut project = Project {
dir: dir_path,
project_langs: None,
is_git: None,
generic_gitignore: None,
gitignore_ruleset: None,
code_stats: None,
};
project.is_git()?;
Ok(project)
}
pub fn parse(&mut self) -> Result<()> {
self.add_langs()?;
self.add_gitignore()?;
self.get_rules()?;
Ok(())
}
pub fn get_code_stats(&mut self) -> Result<Option<HashMap<String, Count>>> {
let stats = code::dir_stats(&self.dir, &self.gitignore_ruleset)?;
self.code_stats = stats.clone();
Ok(stats)
}
pub fn get_content(
&self,
show_hidden: &bool,
show_ignored: &bool,
parents_only: &bool,
) -> Result<Vec<PathBuf>> {
let dir_str = self.dir.to_str().unwrap();
let walker = WalkDir::new(dir_str).into_iter();
let ruleset = self.gitignore_ruleset.as_ref().unwrap();
let mut res: Vec<PathBuf> = vec![];
for entry in walker.filter_entry(|e| {
if e.depth() == 0 {
return true;
}
let mut filters: Vec<bool> = vec![];
if *show_hidden {
if !code::is_hidden(e) {
filters.push(false);
} else {
filters.push(true);
}
}
if *show_ignored {
if !code::is_ignored(&ruleset, e) {
filters.push(false);
} else {
filters.push(true);
}
}
if filters.len() == 0 {
filters.push(!code::is_hidden(e) && !code::is_ignored(&ruleset, e))
}
!filters.contains(&false)
}) {
match &entry {
Ok(e) => {
if e.depth() > 0 {
let d = e.clone();
if *parents_only {
let p = d.path().parent().unwrap().to_path_buf();
if !res.contains(&p) {
res.push(d.path().to_path_buf());
}
}
else{
res.push(d.path().to_path_buf());
}
}
}
_ => (),
}
}
Ok(res)
}
pub fn is_ignored(&self, path_str: &str) -> Option<IsIgnored> {
let mut blank_ignored = IsIgnored {
exists: false,
is_dir: false,
is_ignored: false,
};
let is_ignored = match &self.gitignore_ruleset {
Some(ruleset) => {
let path = PathBuf::from(path_str);
let path = if path.is_relative() {
let mut path = self.dir.clone();
path.push(path_str);
path
} else {
path
};
let re = Regex::new(r"\.\w{2}$").unwrap();
let mut is_dir = !re.is_match(path_str);
if path.exists() {
blank_ignored.exists = true;
is_dir = path.metadata().expect("Cannot get metadata").is_dir();
}
blank_ignored.is_dir = is_dir;
blank_ignored.is_ignored = ruleset.is_ignored(path, is_dir);
blank_ignored
}
_ => blank_ignored,
};
Some(is_ignored)
}
pub fn set_gitignore(&mut self, git_str: &str, update_existing: &bool) -> Result<()> {
let mut ignore_text = match self.generic_gitignore.clone() {
Some(gitignore) => {
if *update_existing {
gitignore.clone()
} else {
vec![]
}
}
_ => vec![],
};
ignore_text.push(format!("\n {}", git_str));
self.generic_gitignore = Some(ignore_text);
self.get_rules()?;
Ok(())
}
pub fn use_project_gitignore(&mut self, update_generic: &bool) -> Result<()> {
let mut path = self.dir.clone();
path.push(".gitignore");
let gitignore = if path.exists() {
match read_to_string(path) {
Ok(s) => s,
_ => "".into(),
}
} else {
"".into()
};
if *update_generic {
self.set_gitignore(&gitignore[..], &true)?;
} else {
self.generic_gitignore = Some(vec![gitignore]);
self.get_rules()?;
}
Ok(())
}
fn get_rules(&mut self) -> Result<()> {
let dir = &self.dir;
let empty_ruleset = ruleset::RuleSet::new(&dir, vec![""])?;
let rule_set: ruleset::RuleSet = match &self.generic_gitignore {
Some(git_ignores) => {
let content = git_ignores.join("\n\n");
match ruleset::load_str(&dir, &content[..]) {
Ok(ruleset) => ruleset,
_ => empty_ruleset,
}
}
_ => empty_ruleset,
};
self.gitignore_ruleset = Some(rule_set);
Ok(())
}
fn add_langs(&mut self) -> Result<()> {
let langs = Some(detector::detect_lang_from_dir(&self.dir)?);
self.project_langs = langs.clone();
Ok(())
}
fn add_gitignore(&mut self) -> Result<()> {
let git_ignores = detector::get_lang_gitignore(&self.project_langs)?;
self.generic_gitignore = git_ignores.clone();
Ok(())
}
fn is_git(&mut self) -> Result<()> {
let mut dir = self.dir.clone();
dir.push(".git");
self.is_git = Some(dir.exists());
Ok(())
}
}