use crate::query::options::LineTypes;
use serde::{Deserialize, Serialize};
use std::ops::{Add, AddAssign, Sub, SubAssign};
use std::path::PathBuf;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Locs {
pub code: u64,
pub tests: u64,
pub examples: u64,
pub docs: u64,
pub comments: u64,
pub blanks: u64,
pub total: u64,
}
impl Locs {
pub fn new() -> Self {
Self::default()
}
pub fn total(&self) -> u64 {
self.total
}
pub fn total_logic(&self) -> u64 {
self.code + self.tests + self.examples
}
pub fn recompute_total(&mut self) {
self.total =
self.code + self.tests + self.examples + self.docs + self.comments + self.blanks;
}
pub fn filter(&self, types: LineTypes) -> Self {
Self {
code: if types.code { self.code } else { 0 },
tests: if types.tests { self.tests } else { 0 },
examples: if types.examples { self.examples } else { 0 },
docs: if types.docs { self.docs } else { 0 },
comments: if types.comments { self.comments } else { 0 },
blanks: if types.blanks { self.blanks } else { 0 },
total: self.total, }
}
}
impl Add for Locs {
type Output = Self;
fn add(self, other: Self) -> Self {
Self {
code: self.code + other.code,
tests: self.tests + other.tests,
examples: self.examples + other.examples,
docs: self.docs + other.docs,
comments: self.comments + other.comments,
blanks: self.blanks + other.blanks,
total: self.total + other.total,
}
}
}
impl AddAssign for Locs {
fn add_assign(&mut self, other: Self) {
self.code += other.code;
self.tests += other.tests;
self.examples += other.examples;
self.docs += other.docs;
self.comments += other.comments;
self.blanks += other.blanks;
self.total += other.total;
}
}
impl Sub for Locs {
type Output = Self;
fn sub(self, other: Self) -> Self {
Self {
code: self.code.saturating_sub(other.code),
tests: self.tests.saturating_sub(other.tests),
examples: self.examples.saturating_sub(other.examples),
docs: self.docs.saturating_sub(other.docs),
comments: self.comments.saturating_sub(other.comments),
blanks: self.blanks.saturating_sub(other.blanks),
total: self.total.saturating_sub(other.total),
}
}
}
impl SubAssign for Locs {
fn sub_assign(&mut self, other: Self) {
self.code = self.code.saturating_sub(other.code);
self.tests = self.tests.saturating_sub(other.tests);
self.examples = self.examples.saturating_sub(other.examples);
self.docs = self.docs.saturating_sub(other.docs);
self.comments = self.comments.saturating_sub(other.comments);
self.blanks = self.blanks.saturating_sub(other.blanks);
self.total = self.total.saturating_sub(other.total);
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FileStats {
pub path: PathBuf,
pub stats: Locs,
}
impl FileStats {
pub fn new(path: PathBuf, stats: Locs) -> Self {
Self { path, stats }
}
pub fn filter(&self, types: LineTypes) -> Self {
Self {
path: self.path.clone(),
stats: self.stats.filter(types),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ModuleStats {
pub name: String,
pub stats: Locs,
pub files: Vec<PathBuf>,
}
impl ModuleStats {
pub fn new(name: String) -> Self {
Self {
name,
stats: Locs::new(),
files: Vec::new(),
}
}
pub fn add_file(&mut self, path: PathBuf, stats: Locs) {
self.stats += stats;
self.files.push(path);
}
pub fn filter(&self, types: LineTypes) -> Self {
Self {
name: self.name.clone(),
stats: self.stats.filter(types),
files: self.files.clone(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CrateStats {
pub name: String,
pub path: PathBuf,
pub stats: Locs,
pub files: Vec<FileStats>,
}
impl CrateStats {
pub fn new(name: String, path: PathBuf) -> Self {
Self {
name,
path,
stats: Locs::new(),
files: Vec::new(),
}
}
pub fn add_file(&mut self, file_stats: FileStats) {
self.stats += file_stats.stats;
self.files.push(file_stats);
}
pub fn filter(&self, types: LineTypes) -> Self {
Self {
name: self.name.clone(),
path: self.path.clone(),
stats: self.stats.filter(types),
files: self.files.iter().map(|f| f.filter(types)).collect(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_locs_default() {
let locs = Locs::new();
assert_eq!(locs.code, 0);
assert_eq!(locs.tests, 0);
assert_eq!(locs.examples, 0);
assert_eq!(locs.docs, 0);
assert_eq!(locs.comments, 0);
assert_eq!(locs.blanks, 0);
assert_eq!(locs.total, 0);
assert_eq!(locs.total(), 0);
}
#[test]
fn test_locs_total() {
let locs = Locs {
code: 100,
tests: 50,
examples: 20,
docs: 30,
comments: 10,
blanks: 15,
total: 225,
};
assert_eq!(locs.total(), 225);
assert_eq!(locs.total_logic(), 170);
}
#[test]
fn test_locs_add() {
let a = Locs {
code: 100,
tests: 50,
examples: 20,
docs: 30,
comments: 10,
blanks: 15,
total: 225,
};
let b = Locs {
code: 50,
tests: 25,
examples: 10,
docs: 15,
comments: 5,
blanks: 10,
total: 115,
};
let sum = a + b;
assert_eq!(sum.code, 150);
assert_eq!(sum.tests, 75);
assert_eq!(sum.examples, 30);
assert_eq!(sum.docs, 45);
assert_eq!(sum.comments, 15);
assert_eq!(sum.blanks, 25);
assert_eq!(sum.total, 340);
}
#[test]
fn test_locs_filter() {
let locs = Locs {
code: 100,
tests: 50,
examples: 20,
docs: 30,
comments: 10,
blanks: 15,
total: 225,
};
let code_only = locs.filter(LineTypes::new().with_code());
assert_eq!(code_only.code, 100);
assert_eq!(code_only.tests, 0);
assert_eq!(code_only.examples, 0);
assert_eq!(code_only.docs, 0);
assert_eq!(code_only.comments, 0);
assert_eq!(code_only.blanks, 0);
assert_eq!(code_only.total, 225);
let code_tests = locs.filter(LineTypes::new().with_code().with_tests());
assert_eq!(code_tests.code, 100);
assert_eq!(code_tests.tests, 50);
assert_eq!(code_tests.examples, 0);
assert_eq!(code_tests.total, 225); }
#[test]
fn test_recompute_total() {
let mut locs = Locs {
code: 100,
tests: 50,
examples: 20,
docs: 30,
comments: 10,
blanks: 15,
total: 0, };
locs.recompute_total();
assert_eq!(locs.total, 225);
}
#[test]
fn test_locs_add_assign() {
let mut a = Locs {
code: 10,
tests: 5,
examples: 2,
docs: 3,
comments: 1,
blanks: 4,
total: 25,
};
a += Locs {
code: 1,
tests: 2,
examples: 3,
docs: 4,
comments: 5,
blanks: 6,
total: 21,
};
assert_eq!(a.code, 11);
assert_eq!(a.tests, 7);
assert_eq!(a.examples, 5);
assert_eq!(a.docs, 7);
assert_eq!(a.comments, 6);
assert_eq!(a.blanks, 10);
assert_eq!(a.total, 46);
}
#[test]
fn test_locs_sub_basic() {
let a = Locs {
code: 100,
tests: 50,
examples: 20,
docs: 30,
comments: 10,
blanks: 15,
total: 225,
};
let b = Locs {
code: 40,
tests: 10,
examples: 5,
docs: 10,
comments: 4,
blanks: 5,
total: 74,
};
let diff = a - b;
assert_eq!(diff.code, 60);
assert_eq!(diff.tests, 40);
assert_eq!(diff.examples, 15);
assert_eq!(diff.docs, 20);
assert_eq!(diff.comments, 6);
assert_eq!(diff.blanks, 10);
assert_eq!(diff.total, 151);
}
#[test]
fn test_locs_sub_saturates_at_zero_no_panic() {
let small = Locs {
code: 1,
tests: 1,
examples: 1,
docs: 1,
comments: 1,
blanks: 1,
total: 6,
};
let large = Locs {
code: 100,
tests: 100,
examples: 100,
docs: 100,
comments: 100,
blanks: 100,
total: 600,
};
let diff = small - large;
assert_eq!(diff, Locs::new()); }
#[test]
fn test_locs_sub_assign_saturates_at_zero() {
let mut a = Locs {
code: 5,
tests: 0,
examples: 3,
docs: 0,
comments: 2,
blanks: 0,
total: 10,
};
a -= Locs {
code: 10, tests: 0,
examples: 1,
docs: 7, comments: 2,
blanks: 0,
total: 20, };
assert_eq!(a.code, 0);
assert_eq!(a.tests, 0);
assert_eq!(a.examples, 2);
assert_eq!(a.docs, 0);
assert_eq!(a.comments, 0);
assert_eq!(a.blanks, 0);
assert_eq!(a.total, 0);
}
#[test]
fn test_locs_filter_empty_zeros_all_but_total() {
let locs = Locs {
code: 7,
tests: 8,
examples: 9,
docs: 1,
comments: 2,
blanks: 3,
total: 30,
};
let filtered = locs.filter(LineTypes::new());
assert_eq!(filtered.code, 0);
assert_eq!(filtered.tests, 0);
assert_eq!(filtered.examples, 0);
assert_eq!(filtered.docs, 0);
assert_eq!(filtered.comments, 0);
assert_eq!(filtered.blanks, 0);
assert_eq!(filtered.total, 30); }
#[test]
fn test_file_stats_filter_preserves_path_and_total() {
let locs = Locs {
code: 10,
tests: 5,
examples: 0,
docs: 2,
comments: 1,
blanks: 3,
total: 21,
};
let fs = FileStats::new(PathBuf::from("src/lib.rs"), locs);
let filtered = fs.filter(LineTypes::new().with_tests());
assert_eq!(filtered.path, PathBuf::from("src/lib.rs"));
assert_eq!(filtered.stats.tests, 5);
assert_eq!(filtered.stats.code, 0);
assert_eq!(filtered.stats.total, 21); }
#[test]
fn test_module_stats_add_file_aggregates_and_tracks_files() {
let mut module = ModuleStats::new("foo".to_string());
assert_eq!(module.stats, Locs::new());
assert!(module.files.is_empty());
let a = Locs {
code: 10,
tests: 0,
examples: 0,
docs: 0,
comments: 0,
blanks: 2,
total: 12,
};
let b = Locs {
code: 5,
tests: 4,
examples: 0,
docs: 1,
comments: 0,
blanks: 0,
total: 10,
};
module.add_file(PathBuf::from("foo/a.rs"), a);
module.add_file(PathBuf::from("foo/b.rs"), b);
assert_eq!(module.stats.code, 15);
assert_eq!(module.stats.tests, 4);
assert_eq!(module.stats.blanks, 2);
assert_eq!(module.stats.docs, 1);
assert_eq!(module.stats.total, 22);
assert_eq!(
module.files,
vec![PathBuf::from("foo/a.rs"), PathBuf::from("foo/b.rs")]
);
}
#[test]
fn test_crate_stats_add_file_aggregates_and_keeps_filestats() {
let mut krate = CrateStats::new("mylib".to_string(), PathBuf::from("/work/mylib"));
let f1 = FileStats::new(
PathBuf::from("/work/mylib/src/lib.rs"),
Locs {
code: 20,
tests: 0,
examples: 0,
docs: 5,
comments: 0,
blanks: 0,
total: 25,
},
);
let f2 = FileStats::new(
PathBuf::from("/work/mylib/tests/it.rs"),
Locs {
code: 0,
tests: 30,
examples: 0,
docs: 0,
comments: 0,
blanks: 0,
total: 30,
},
);
krate.add_file(f1);
krate.add_file(f2);
assert_eq!(krate.stats.code, 20);
assert_eq!(krate.stats.tests, 30);
assert_eq!(krate.stats.docs, 5);
assert_eq!(krate.stats.total, 55);
assert_eq!(krate.files.len(), 2);
assert_eq!(krate.files[1].stats.tests, 30);
}
#[test]
fn test_crate_stats_filter_applies_to_aggregate_and_each_file() {
let mut krate = CrateStats::new("mylib".to_string(), PathBuf::from("/work/mylib"));
krate.add_file(FileStats::new(
PathBuf::from("/work/mylib/src/lib.rs"),
Locs {
code: 20,
tests: 7,
examples: 0,
docs: 5,
comments: 0,
blanks: 0,
total: 32,
},
));
let filtered = krate.filter(LineTypes::new().with_code());
assert_eq!(filtered.stats.code, 20);
assert_eq!(filtered.stats.tests, 0);
assert_eq!(filtered.stats.total, 32);
assert_eq!(filtered.files[0].stats.code, 20);
assert_eq!(filtered.files[0].stats.tests, 0);
assert_eq!(filtered.files[0].stats.total, 32);
assert_eq!(filtered.name, "mylib");
assert_eq!(filtered.path, PathBuf::from("/work/mylib"));
}
}