use std::path::PathBuf;
use serde::Serialize;
use tokei::{Config, LanguageType};
use super::tokenizer::Tokenizer;
use crate::functions::FnStats;
use crate::units::{Bytes, Lines, Tokens};
use crate::walk::FileEntry;
#[derive(Debug, Serialize)]
pub struct FileStats {
pub path: PathBuf,
pub language: String,
pub bytes: Bytes,
pub lines: Lines,
pub code: Lines,
pub comments: Lines,
pub blanks: Lines,
pub tokens: Tokens,
#[serde(skip_serializing_if = "Option::is_none")]
pub functions: Option<Vec<FnStats>>,
}
pub struct AnalyzeInput {
pub entry: FileEntry,
pub tokenizer: Tokenizer,
pub with_functions: bool,
}
impl FileStats {
pub fn analyze(input: AnalyzeInput) -> Option<Self> {
let AnalyzeInput {
entry: FileEntry { real, display },
tokenizer,
with_functions,
} = input;
let text = std::fs::read_to_string(&real).ok()?;
let lang = LanguageType::from_path(&real, &Config::default());
let LineBreakdown {
code,
comments,
blanks,
} = LineBreakdown::of(lang, &text);
Some(Self {
language: lang.map_or("Other".to_string(), |l| l.name().to_string()),
bytes: Bytes(text.len()),
lines: Lines(text.lines().count()),
code,
comments,
blanks,
tokens: tokenizer.count(&text),
functions: with_functions
.then(|| FnStats::extract(&real, &text, tokenizer))
.flatten(),
path: display,
})
}
}
struct LineBreakdown {
code: Lines,
comments: Lines,
blanks: Lines,
}
impl LineBreakdown {
fn of(lang: Option<LanguageType>, text: &str) -> Self {
let Some(lang) = lang else {
let blanks = text.lines().filter(|l| l.trim().is_empty()).count();
return Self {
code: Lines(text.lines().count().saturating_sub(blanks)),
comments: Lines(0),
blanks: Lines(blanks),
};
};
let s = lang.parse_from_str(text, &Config::default()).summarise();
Self {
code: Lines(s.code),
comments: Lines(s.comments),
blanks: Lines(s.blanks),
}
}
}