use crate::consts::{
BLANKS_COLUMN_WIDTH, CODE_COLUMN_WIDTH, COMMENTS_COLUMN_WIDTH, LINES_COLUMN_WIDTH,
};
use crate::LanguageType;
use std::{collections::BTreeMap, fmt, ops, path::PathBuf};
#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)]
#[non_exhaustive]
pub struct CodeStats {
pub blanks: usize,
pub code: usize,
pub comments: usize,
pub blobs: BTreeMap<LanguageType, CodeStats>,
}
impl CodeStats {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn lines(&self) -> usize {
self.blanks + self.code + self.comments
}
#[must_use]
pub fn summarise(&self) -> Self {
let mut summary = self.clone();
for (_, stats) in std::mem::take(&mut summary.blobs) {
let child_summary = stats.summarise();
summary.blanks += child_summary.blanks;
summary.comments += child_summary.comments;
summary.code += child_summary.code;
}
summary
}
}
impl ops::AddAssign for CodeStats {
fn add_assign(&mut self, rhs: Self) {
self.add_assign(&rhs);
}
}
impl ops::AddAssign<&'_ CodeStats> for CodeStats {
fn add_assign(&mut self, rhs: &'_ CodeStats) {
self.blanks += rhs.blanks;
self.code += rhs.code;
self.comments += rhs.comments;
for (language, stats) in &rhs.blobs {
*self.blobs.entry(*language).or_default() += stats;
}
}
}
#[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
#[non_exhaustive]
pub struct Report {
pub stats: CodeStats,
pub name: PathBuf,
}
impl Report {
#[must_use]
pub fn new(name: PathBuf) -> Self {
Self {
name,
..Self::default()
}
}
}
impl ops::AddAssign<CodeStats> for Report {
fn add_assign(&mut self, rhs: CodeStats) {
self.stats += rhs;
}
}
#[doc(hidden)]
#[must_use]
pub fn find_char_boundary(s: &str, index: usize) -> usize {
for i in 0..4 {
if s.is_char_boundary(index + i) {
return index + i;
}
}
unreachable!();
}
macro_rules! display_stats {
($f:expr, $this:expr, $name:expr, $max:expr) => {
write!(
$f,
" {: <max$} {:>LINES_COLUMN_WIDTH$} {:>CODE_COLUMN_WIDTH$} {:>COMMENTS_COLUMN_WIDTH$} {:>BLANKS_COLUMN_WIDTH$}",
$name,
$this.stats.lines(),
$this.stats.code,
$this.stats.comments,
$this.stats.blanks,
max = $max
)
};
}
impl fmt::Display for Report {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let name = self.name.to_string_lossy();
let name_length = name.len();
let max_len = f.width().unwrap_or(27) + 2;
if name_length <= max_len {
display_stats!(f, self, name, max_len)
} else {
let mut formatted = String::from("|");
let from = find_char_boundary(&name, name_length + 1 - max_len);
formatted.push_str(&name[from..]);
display_stats!(f, self, formatted, max_len)
}
}
}