Initial upload: GitPulse - Developer Productivity Analyzer CLI tool
This commit is contained in:
200
src/git/filter.rs
Normal file
200
src/git/filter.rs
Normal file
@@ -0,0 +1,200 @@
|
||||
use crate::git::Commit;
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use chrono::{DateTime, Duration, Local, NaiveDate, ParseError, Utc};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum TimePeriod {
|
||||
Days(u32),
|
||||
Weeks(u32),
|
||||
Months(u32),
|
||||
Years(u32),
|
||||
Custom(DateTime<Utc>, DateTime<Utc>),
|
||||
}
|
||||
|
||||
impl TimePeriod {
|
||||
pub fn days(n: u32) -> Self {
|
||||
Self::Days(n)
|
||||
}
|
||||
|
||||
pub fn weeks(n: u32) -> Self {
|
||||
Self::Weeks(n)
|
||||
}
|
||||
|
||||
pub fn months(n: u32) -> Self {
|
||||
Self::Months(n)
|
||||
}
|
||||
|
||||
pub fn years(n: u32) -> Self {
|
||||
Self::Years(n)
|
||||
}
|
||||
|
||||
pub fn custom(start: DateTime<Utc>, end: DateTime<Utc>) -> Result<Self> {
|
||||
if start > end {
|
||||
bail!("Start date must be before end date");
|
||||
}
|
||||
Ok(Self::Custom(start, end))
|
||||
}
|
||||
|
||||
pub fn to_range(&self) -> (DateTime<Utc>, DateTime<Utc>) {
|
||||
let now = Utc::now();
|
||||
match self {
|
||||
TimePeriod::Days(n) => (now - Duration::days(*n as i64), now),
|
||||
TimePeriod::Weeks(n) => (now - Duration::weeks(*n as i64), now),
|
||||
TimePeriod::Months(n) => (now - Duration::days((*n as i64) * 30), now),
|
||||
TimePeriod::Years(n) => (now - Duration::days((*n as i64) * 365), now),
|
||||
TimePeriod::Custom(start, end) => (*start, *end),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for TimePeriod {
|
||||
type Err = ParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if s.ends_with('d') {
|
||||
let n: u32 = s[..s.len() - 1].parse()?;
|
||||
Ok(TimePeriod::Days(n))
|
||||
} else if s.ends_with('w') {
|
||||
let n: u32 = s[..s.len() - 1].parse()?;
|
||||
Ok(TimePeriod::Weeks(n))
|
||||
} else if s.ends_with('m') {
|
||||
let n: u32 = s[..s.len() - 1].parse()?;
|
||||
Ok(TimePeriod::Months(n))
|
||||
} else if s.ends_with('y') {
|
||||
let n: u32 = s[..s.len() - 1].parse()?;
|
||||
Ok(TimePeriod::Years(n))
|
||||
} else if s.contains(':') {
|
||||
let parts: Vec<&str> = s.split(':').collect();
|
||||
if parts.len() == 2 {
|
||||
let start = NaiveDate::parse_from_str(parts[0], "%Y-%m-%d")?
|
||||
.and_hms_opt(0, 0, 0)
|
||||
.unwrap();
|
||||
let end = NaiveDate::parse_from_str(parts[1], "%Y-%m-%d")?
|
||||
.and_hms_opt(23, 59, 59)
|
||||
.unwrap();
|
||||
return Ok(TimePeriod::Custom(
|
||||
DateTime::from_utc(start, Utc),
|
||||
DateTime::from_utc(end, Utc),
|
||||
));
|
||||
}
|
||||
Err(ParseError)
|
||||
} else {
|
||||
let date = NaiveDate::parse_from_str(s, "%Y-%m-%d")?
|
||||
.and_hms_opt(0, 0, 0)
|
||||
.unwrap();
|
||||
let start = DateTime::from_utc(date, Utc);
|
||||
let end = start + Duration::days(1);
|
||||
Ok(TimePeriod::Custom(start, end))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TimeFilter {
|
||||
since: Option<DateTime<Utc>>,
|
||||
until: Option<DateTime<Utc>>,
|
||||
period: Option<TimePeriod>,
|
||||
}
|
||||
|
||||
impl TimeFilter {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
since: None,
|
||||
until: None,
|
||||
period: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn since(mut self, date: DateTime<Utc>) -> Self {
|
||||
self.since = Some(date);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn until(mut self, date: DateTime<Utc>) -> Self {
|
||||
self.until = Some(date);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn period(mut self, period: TimePeriod) -> Self {
|
||||
self.period = Some(period);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn days_back(mut self, days: u32) -> Self {
|
||||
let now = Utc::now();
|
||||
self.since = Some(now - Duration::days(days as i64));
|
||||
self.until = Some(now);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> TimeFilter {
|
||||
let (since, until) = if let Some(period) = self.period {
|
||||
let (start, end) = period.to_range();
|
||||
(Some(start), Some(end))
|
||||
} else {
|
||||
(self.since, self.until)
|
||||
};
|
||||
TimeFilter {
|
||||
since,
|
||||
until,
|
||||
period: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contains(&self, commit: &Commit) -> bool {
|
||||
let time = commit.time();
|
||||
if let Some(since) = self.since {
|
||||
if time < since {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(until) = self.until {
|
||||
if time > until {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TimeFilter {
|
||||
fn default() -> Self {
|
||||
Self::new().days_back(30)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_date(s: &str) -> Result<DateTime<Utc>> {
|
||||
let formats = [
|
||||
"%Y-%m-%d",
|
||||
"%Y/%m/%d",
|
||||
"%d-%m-%Y",
|
||||
"%d/%m/%Y",
|
||||
"%Y-%m-%dT%H:%M:%S",
|
||||
"%Y-%m-%d %H:%M:%S",
|
||||
];
|
||||
|
||||
for fmt in &formats {
|
||||
if let Ok(dt) = chrono::DateTime::parse_from_str(s, fmt) {
|
||||
return Ok(dt.with_timezone(&Utc));
|
||||
}
|
||||
}
|
||||
|
||||
if s.ends_with('d') {
|
||||
let n: u32 = s[..s.len() - 1].parse()?;
|
||||
let now = Utc::now();
|
||||
return Ok(now - Duration::days(n as i64));
|
||||
} else if s.ends_with('w') {
|
||||
let n: u32 = s[..s.len() - 1].parse()?;
|
||||
let now = Utc::now();
|
||||
return Ok(now - Duration::weeks(n as i64));
|
||||
} else if s.ends_with('m') {
|
||||
let n: u32 = s[..s.len() - 1].parse()?;
|
||||
let now = Utc::now();
|
||||
return Ok(now - Duration::days((n as i64) * 30));
|
||||
} else if s.ends_with('y') {
|
||||
let n: u32 = s[..s.len() - 1].parse()?;
|
||||
let now = Utc::now();
|
||||
return Ok(now - Duration::days((n as i64) * 365));
|
||||
}
|
||||
|
||||
Err(anyhow!("Cannot parse date: {}", s))
|
||||
}
|
||||
Reference in New Issue
Block a user