working on translating cli options to usable args.
This commit is contained in:
parent
d113289fd2
commit
0293018f85
17
Cargo.lock
generated
17
Cargo.lock
generated
|
@ -275,6 +275,7 @@ dependencies = [
|
|||
"git2",
|
||||
"iana-time-zone",
|
||||
"itertools",
|
||||
"nom",
|
||||
"once_cell",
|
||||
"proptest",
|
||||
"rand",
|
||||
|
@ -439,6 +440,22 @@ version = "2.7.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.17"
|
||||
|
|
|
@ -15,6 +15,7 @@ clap = { version = "4.4.18", features = ["derive"] }
|
|||
git2 = "0.18.2"
|
||||
iana-time-zone = "0.1.60"
|
||||
itertools = "0.12.1"
|
||||
nom = "7.1.3"
|
||||
once_cell = "1.19.0"
|
||||
proptest = "1.4.0"
|
||||
rand = {version="0.8.5", features=["small_rng"]}
|
||||
|
|
87
src/args.rs
87
src/args.rs
|
@ -1,5 +1,15 @@
|
|||
use chrono::{NaiveDateTime, TimeZone};
|
||||
#![allow(dead_code)]
|
||||
use crate::err::GitErrors;
|
||||
use chrono::prelude::*;
|
||||
use chrono::NaiveDateTime;
|
||||
use chrono_tz::Tz;
|
||||
use clap::Parser;
|
||||
use git2::Commit;
|
||||
use git2::Oid;
|
||||
use git2::Repository;
|
||||
use git2::Sort;
|
||||
|
||||
use crate::time::DateTimeRange;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(author, version, about)]
|
||||
|
@ -19,7 +29,78 @@ pub(crate) struct Cli {
|
|||
pub(crate) last_commit: Option<String>,
|
||||
|
||||
/// Timezone to place commits in, will default to local tz. Must be a Tz identifier as listed
|
||||
/// on https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. Defaults to local tz
|
||||
/// on <https://en.wikipedia.org/wiki/List_of_tz_database_time_zones>. Defaults to local tz
|
||||
#[arg(short, long)]
|
||||
pub(crate) timezone: Option<chrono_tz::Tz>
|
||||
pub(crate) timezone: Option<chrono_tz::Tz>,
|
||||
}
|
||||
|
||||
pub(crate) struct ProgramOptions {
|
||||
pub(crate) start_time: DateTime<Tz>,
|
||||
pub(crate) end_time: DateTime<Tz>,
|
||||
pub(crate) allowed_times: Vec<DateTimeRange<Tz>>,
|
||||
pub(crate) first_commit: Oid,
|
||||
pub(crate) last_commit: Oid,
|
||||
}
|
||||
|
||||
impl TryFrom<Cli> for ProgramOptions {
|
||||
type Error = GitErrors;
|
||||
|
||||
fn try_from(cli: Cli) -> Result<Self, Self::Error> {
|
||||
let tz = cli
|
||||
.timezone
|
||||
.unwrap_or_else(|| get_local_timezone().expect("Could not get local timezone"));
|
||||
let start_time = tz.from_local_datetime(&cli.start_time).unwrap();
|
||||
let end_time = if let Some(time) = cli.end_time {
|
||||
tz.from_local_datetime(&time).unwrap()
|
||||
} else {
|
||||
Utc::now().with_timezone(&tz)
|
||||
};
|
||||
let repo = if let Ok(repo) = Repository::open_from_env() {
|
||||
repo
|
||||
} else {
|
||||
return Err(GitErrors::NotARepo);
|
||||
};
|
||||
let first_commit = if let Some(sha) = cli.first_commit {
|
||||
if let Ok(commit) = repo.find_commit_by_prefix(&sha) {
|
||||
commit
|
||||
} else {
|
||||
return Err(GitErrors::NotACommit(sha));
|
||||
}
|
||||
} else {
|
||||
repo.head()
|
||||
.expect("could not get head")
|
||||
.peel_to_commit()
|
||||
.expect("could not peel head to commit")
|
||||
}
|
||||
.id();
|
||||
let last_commit = if let Some(sha) = cli.last_commit {
|
||||
if let Ok(commit) = repo.find_commit_by_prefix(&sha) {
|
||||
commit.id()
|
||||
} else {
|
||||
return Err(GitErrors::NotACommit(sha));
|
||||
}
|
||||
} else {
|
||||
let mut sorting = Sort::REVERSE;
|
||||
sorting.insert(Sort::TOPOLOGICAL);
|
||||
sorting.insert(Sort::TIME);
|
||||
let mut revwalker = repo.revwalk().unwrap();
|
||||
revwalker.set_sorting(sorting).unwrap();
|
||||
revwalker.simplify_first_parent().unwrap();
|
||||
revwalker.next().unwrap().unwrap()
|
||||
};
|
||||
Ok(ProgramOptions {
|
||||
start_time,
|
||||
end_time,
|
||||
allowed_times: todo!(),
|
||||
first_commit,
|
||||
last_commit,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn get_local_timezone() -> Result<Tz, String> {
|
||||
match iana_time_zone::get_timezone() {
|
||||
Ok(tz_string) => tz_string.parse(),
|
||||
Err(e) => Err(e.to_string()),
|
||||
}
|
||||
}
|
||||
|
|
15
src/err.rs
15
src/err.rs
|
@ -1 +1,14 @@
|
|||
use thiserror;
|
||||
#![allow(dead_code)]
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub(crate) enum GitErrors {
|
||||
#[error("end is not a parent of start")]
|
||||
NotAParent,
|
||||
|
||||
#[error("{0} is not a commit hash in the repo")]
|
||||
NotACommit(String),
|
||||
|
||||
#[error("Could not find repository in current path")]
|
||||
NotARepo,
|
||||
}
|
||||
|
|
31
src/git.rs
31
src/git.rs
|
@ -1,19 +1,12 @@
|
|||
#![allow(dead_code)]
|
||||
use chrono::Duration;
|
||||
use git2::{Commit, Repository};
|
||||
use itertools::Itertools;
|
||||
use thiserror::Error;
|
||||
use crate::err::GitErrors;
|
||||
|
||||
use crate::time::{distribute_across_ranges_with_jitter, TimeRange};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub(crate) enum GitErrors {
|
||||
#[error("end is not a parent of start")]
|
||||
NotAParent,
|
||||
|
||||
#[error("{0} is not a commit hash in the repo")]
|
||||
NotACommit(String),
|
||||
}
|
||||
use crate::time::{distribute_across_ranges_with_jitter, DateTimeRange};
|
||||
|
||||
// might be able to replace with graph_ahead_behind and graph_decendant_of?
|
||||
/// find the number of commits between start and end,
|
||||
/// where end is a parent commit of start
|
||||
fn get_no_of_commits(start: &Commit, end: &Commit) -> Result<u32, GitErrors> {
|
||||
|
@ -26,21 +19,11 @@ fn get_no_of_commits(start: &Commit, end: &Commit) -> Result<u32, GitErrors> {
|
|||
// will need to do an in memory rebase, ammending each commit with a new signature.
|
||||
pub fn rebase_commits_across_timerange<Tz: chrono::TimeZone>(
|
||||
repo: &Repository,
|
||||
start_sha: &str,
|
||||
end_sha: &str,
|
||||
ranges: &[TimeRange<Tz>],
|
||||
start_commit: &Commit,
|
||||
end_commit: &Commit,
|
||||
ranges: &[DateTimeRange<Tz>],
|
||||
max_jitter: &Duration,
|
||||
) -> Result<(), GitErrors> {
|
||||
let start_commit = if let Ok(commit) = repo.find_commit_by_prefix(start_sha) {
|
||||
commit
|
||||
} else {
|
||||
return Err(GitErrors::NotACommit(start_sha.to_string()));
|
||||
};
|
||||
let end_commit = if let Ok(commit) = repo.find_commit_by_prefix(end_sha) {
|
||||
commit
|
||||
} else {
|
||||
return Err(GitErrors::NotACommit(end_sha.to_string()));
|
||||
};
|
||||
let num_commits = get_no_of_commits(&start_commit, &end_commit)?;
|
||||
let times = distribute_across_ranges_with_jitter(ranges, num_commits, max_jitter);
|
||||
todo!()
|
||||
|
|
16
src/main.rs
16
src/main.rs
|
@ -10,24 +10,8 @@ use clap::Parser;
|
|||
fn main() {
|
||||
let cli = args::Cli::parse();
|
||||
println!("{:#?}", cli);
|
||||
let tz = cli
|
||||
.timezone
|
||||
.unwrap_or_else(|| get_local_timezone().expect("Could not get local timezone"));
|
||||
let start_time = tz.from_local_datetime(&cli.start_time).unwrap();
|
||||
let end_time = if let Some(time) = cli.end_time {
|
||||
tz.from_local_datetime(&time).unwrap()
|
||||
} else {
|
||||
Utc::now().with_timezone(&tz)
|
||||
};
|
||||
println!(
|
||||
"start_time = {:#?}, end_time = {:#?}, tz = {:#?}",
|
||||
start_time, end_time, tz
|
||||
)
|
||||
}
|
||||
|
||||
fn get_local_timezone() -> Result<Tz, String> {
|
||||
match iana_time_zone::get_timezone() {
|
||||
Ok(tz_string) => tz_string.parse(),
|
||||
Err(e) => Err(e.to_string()),
|
||||
}
|
||||
}
|
||||
|
|
39
src/time.rs
39
src/time.rs
|
@ -1,17 +1,32 @@
|
|||
#![allow(dead_code)]
|
||||
use chrono::{DateTime, Duration};
|
||||
use rand::{rngs::SmallRng, Rng, SeedableRng};
|
||||
|
||||
/// A range of time represented by a start datetime and an end datetime.
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct TimeRange<Tz: chrono::TimeZone> {
|
||||
pub struct DateTimeRange<Tz: chrono::TimeZone> {
|
||||
pub start: DateTime<Tz>,
|
||||
pub end: DateTime<Tz>,
|
||||
}
|
||||
|
||||
impl<Tz: chrono::TimeZone> TimeRange<Tz> {
|
||||
impl<Tz: chrono::TimeZone> DateTimeRange<Tz> {
|
||||
/// calculates the time range that is part of both time ranges, if any
|
||||
pub fn intersection(
|
||||
&self,
|
||||
other: &DateTimeRange<Tz>,
|
||||
) -> Option<DateTimeRange<Tz>> {
|
||||
if self.is_in_range(&other.start) || self.is_in_range(&other.end) {
|
||||
Some(DateTimeRange {
|
||||
start: self.start.max(other.start),
|
||||
end: self.end.min(other.end),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
/// whether a time instance lies within the time range
|
||||
pub fn is_in_range<Otz: chrono::TimeZone>(&self, other: &DateTime<Otz>) -> bool {
|
||||
(self.start <= *other) && (other <= &self.end)
|
||||
(&self.start <= other) && (other <= &self.end)
|
||||
}
|
||||
|
||||
/// the time between the start and end of the time range.
|
||||
|
@ -52,7 +67,7 @@ impl<Tz: chrono::TimeZone> TimeRange<Tz> {
|
|||
/// Distribute a number of events across a set of time ranges evenly,
|
||||
/// applying a specific amount of jitter.
|
||||
pub fn distribute_across_ranges_with_jitter<Tz: chrono::TimeZone>(
|
||||
ranges: &[TimeRange<Tz>],
|
||||
ranges: &[DateTimeRange<Tz>],
|
||||
number: u32,
|
||||
max_jitter: &Duration,
|
||||
) -> Vec<DateTime<Tz>> {
|
||||
|
@ -60,7 +75,7 @@ pub fn distribute_across_ranges_with_jitter<Tz: chrono::TimeZone>(
|
|||
let total_duration: Duration = ranges
|
||||
.iter()
|
||||
.fold(Duration::zero(), |a, r| a + r.duration());
|
||||
let compressed_time = TimeRange {
|
||||
let compressed_time = DateTimeRange {
|
||||
start: ranges[0].start.clone(),
|
||||
end: ranges[0].start.clone() + total_duration,
|
||||
};
|
||||
|
@ -93,7 +108,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_distribute_evenly() {
|
||||
let timerange = TimeRange::<chrono::Utc> {
|
||||
let timerange = DateTimeRange::<chrono::Utc> {
|
||||
start: Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(),
|
||||
end: Utc.with_ymd_and_hms(2024, 1, 2, 0, 0, 0).unwrap(),
|
||||
};
|
||||
|
@ -111,7 +126,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_distribute_with_jitter() {
|
||||
let timerange = TimeRange::<chrono::Utc> {
|
||||
let timerange = DateTimeRange::<chrono::Utc> {
|
||||
start: Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(),
|
||||
end: Utc.with_ymd_and_hms(2024, 1, 2, 0, 0, 0).unwrap(),
|
||||
};
|
||||
|
@ -128,7 +143,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_with_extreme_jitter() {
|
||||
let timerange = TimeRange::<chrono::Utc> {
|
||||
let timerange = DateTimeRange::<chrono::Utc> {
|
||||
start: Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(),
|
||||
end: Utc.with_ymd_and_hms(2024, 1, 2, 0, 0, 0).unwrap(),
|
||||
};
|
||||
|
@ -142,11 +157,11 @@ mod tests {
|
|||
#[test]
|
||||
fn test_distribute_across_ranges_without_jitter() {
|
||||
let ranges = vec![
|
||||
TimeRange::<chrono::Utc> {
|
||||
DateTimeRange::<chrono::Utc> {
|
||||
start: Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(),
|
||||
end: Utc.with_ymd_and_hms(2024, 1, 2, 0, 0, 0).unwrap(),
|
||||
},
|
||||
TimeRange::<chrono::Utc> {
|
||||
DateTimeRange::<chrono::Utc> {
|
||||
start: Utc.with_ymd_and_hms(2024, 1, 5, 0, 0, 0).unwrap(),
|
||||
end: Utc.with_ymd_and_hms(2024, 1, 6, 0, 0, 0).unwrap(),
|
||||
},
|
||||
|
@ -162,11 +177,11 @@ mod tests {
|
|||
#[test]
|
||||
fn test_distribute_across_ranges_with_jitter() {
|
||||
let ranges = vec![
|
||||
TimeRange::<chrono::Utc> {
|
||||
DateTimeRange::<chrono::Utc> {
|
||||
start: Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(),
|
||||
end: Utc.with_ymd_and_hms(2024, 1, 2, 0, 0, 0).unwrap(),
|
||||
},
|
||||
TimeRange::<chrono::Utc> {
|
||||
DateTimeRange::<chrono::Utc> {
|
||||
start: Utc.with_ymd_and_hms(2024, 1, 5, 0, 0, 0).unwrap(),
|
||||
end: Utc.with_ymd_and_hms(2024, 1, 6, 0, 0, 0).unwrap(),
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue