use polars::prelude::*;
use crate::options::{ActivityOptions, Aggregation, FileSelection};
pub mod activity;
pub mod blame;
pub use activity::{author_report, file_author_matrix, file_report, summary};
pub use blame::{blame_report, blame_timeline_report, blame_timeline_report_with_label};
pub fn filter_files(lf: LazyFrame, sel: FileSelection) -> LazyFrame {
let mut out = lf;
if !sel.include_generated {
out = out.filter(col("is_generated").not());
}
if !sel.include_vendored {
out = out.filter(col("is_vendored").not());
}
out
}
pub fn filter_bots(lf: LazyFrame, agg: Aggregation, activity: ActivityOptions) -> LazyFrame {
if !activity.ignore_bots {
return lf;
}
let name_col = format!("canonical_{}_name", agg.aggregate.as_str());
lf.filter(col(&name_col).str().ends_with(lit("[bot]")).not())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::options::{Aggregate, Identify};
fn fixture() -> DataFrame {
df! {
"canonical_author_name" => ["alice", "dependabot[bot]", "bob"],
"canonical_committer_name" => ["alice", "alice", "renovate[bot]"],
"lines" => [10u32, 20, 30],
}
.unwrap()
}
fn agg(a: Aggregate) -> Aggregation {
Aggregation {
aggregate: a,
identify: Identify::Name,
}
}
fn opts(ignore_bots: bool) -> ActivityOptions {
ActivityOptions {
ignore_bots,
..ActivityOptions::default()
}
}
#[test]
fn passthrough_when_ignore_bots_is_false() {
let df = filter_bots(fixture().lazy(), agg(Aggregate::Author), opts(false))
.collect()
.unwrap();
assert_eq!(df.height(), 3);
}
#[test]
fn drops_author_bots_when_aggregate_is_author() {
let df = filter_bots(fixture().lazy(), agg(Aggregate::Author), opts(true))
.collect()
.unwrap();
assert_eq!(df.height(), 2);
let names: Vec<&str> = df
.column("canonical_author_name")
.unwrap()
.str()
.unwrap()
.iter()
.map(|v| v.unwrap())
.collect();
assert!(names.contains(&"alice"));
assert!(names.contains(&"bob"));
assert!(!names.contains(&"dependabot[bot]"));
}
#[test]
fn drops_committer_bots_when_aggregate_is_committer() {
let df = filter_bots(fixture().lazy(), agg(Aggregate::Committer), opts(true))
.collect()
.unwrap();
assert_eq!(df.height(), 2);
let names: Vec<&str> = df
.column("canonical_committer_name")
.unwrap()
.str()
.unwrap()
.iter()
.map(|v| v.unwrap())
.collect();
assert!(names.iter().filter(|&&n| n == "alice").count() == 2);
assert!(!names.contains(&"renovate[bot]"));
}
#[test]
fn matches_only_exact_bot_suffix() {
let df = df! {
"canonical_author_name" => ["alice", "[bot]something", "real[bot]"],
"canonical_committer_name" => ["alice", "alice", "alice"],
"lines" => [1u32, 2, 3],
}
.unwrap();
let out = filter_bots(df.lazy(), agg(Aggregate::Author), opts(true))
.collect()
.unwrap();
assert_eq!(out.height(), 2);
let names: Vec<&str> = out
.column("canonical_author_name")
.unwrap()
.str()
.unwrap()
.iter()
.map(|v| v.unwrap())
.collect();
assert!(names.contains(&"alice"));
assert!(names.contains(&"[bot]something"));
assert!(!names.contains(&"real[bot]"));
}
}