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