Move entries to a different time
This commit is contained in:
parent
05a4582f13
commit
1ac39c69f2
9 changed files with 99 additions and 26 deletions
|
|
@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- `REMIND` statement
|
- `REMIND` statement
|
||||||
|
- `MOVE` entries to a different time
|
||||||
|
|
||||||
## 0.1.0 - 2021-12-20
|
## 0.1.0 - 2021-12-20
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use chrono::NaiveDate;
|
use chrono::{Duration, NaiveDate};
|
||||||
|
|
||||||
use crate::files::commands::{
|
use crate::files::commands::{
|
||||||
self, BirthdaySpec, Command, Done, DoneDate, Note, Spec, Statement, Task,
|
self, BirthdaySpec, Command, Done, DoneDate, Note, Spec, Statement, Task,
|
||||||
};
|
};
|
||||||
use crate::files::primitives::{Span, Spanned};
|
use crate::files::primitives::{Span, Spanned, Time};
|
||||||
use crate::files::SourcedCommand;
|
use crate::files::SourcedCommand;
|
||||||
|
|
||||||
use super::date::Dates;
|
use super::date::Dates;
|
||||||
|
|
@ -220,7 +220,12 @@ impl<'a> CommandState<'a> {
|
||||||
Statement::From(date) => self.from = *date,
|
Statement::From(date) => self.from = *date,
|
||||||
Statement::Until(date) => self.until = *date,
|
Statement::Until(date) => self.until = *date,
|
||||||
Statement::Except(date) => self.eval_except(*date),
|
Statement::Except(date) => self.eval_except(*date),
|
||||||
Statement::Move { span, from, to } => self.eval_move(*span, *from, *to)?,
|
Statement::Move {
|
||||||
|
span,
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
to_time,
|
||||||
|
} => self.eval_move(*span, *from, *to, *to_time)?,
|
||||||
Statement::Remind(delta) => self.eval_remind(delta),
|
Statement::Remind(delta) => self.eval_remind(delta),
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -242,13 +247,31 @@ impl<'a> CommandState<'a> {
|
||||||
self.dated.remove(&date);
|
self.dated.remove(&date);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_move(&mut self, span: Span, from: NaiveDate, to: NaiveDate) -> Result<()> {
|
fn eval_move(
|
||||||
|
&mut self,
|
||||||
|
span: Span,
|
||||||
|
from: NaiveDate,
|
||||||
|
to: Option<NaiveDate>,
|
||||||
|
to_time: Option<Time>,
|
||||||
|
) -> Result<()> {
|
||||||
if let Some(mut entry) = self.dated.remove(&from) {
|
if let Some(mut entry) = self.dated.remove(&from) {
|
||||||
if let Some(dates) = entry.dates {
|
let mut dates = entry.dates.expect("comes from self.dated");
|
||||||
let delta = to - from;
|
|
||||||
entry.dates = Some(dates.move_by(delta));
|
// Determine delta
|
||||||
|
let mut delta = Duration::zero();
|
||||||
|
if let Some(to) = to {
|
||||||
|
delta = delta + (to - dates.root());
|
||||||
}
|
}
|
||||||
self.dated.insert(to, entry);
|
if let Some(to_time) = to_time {
|
||||||
|
if let Some((root, _)) = dates.times() {
|
||||||
|
delta = delta + Duration::minutes(root.minutes_to(to_time));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dates = dates.move_by(delta);
|
||||||
|
entry.dates = Some(dates);
|
||||||
|
self.dated.insert(dates.root(), entry);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(Error::MoveWithoutSource {
|
Err(Error::MoveWithoutSource {
|
||||||
|
|
|
||||||
|
|
@ -103,11 +103,24 @@ impl Dates {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_by(&self, delta: Duration) -> Self {
|
pub fn move_by(&self, delta: Duration) -> Self {
|
||||||
Self {
|
let mut result = *self;
|
||||||
root: self.root + delta,
|
|
||||||
other: self.other + delta,
|
// Modify dates
|
||||||
times: self.times,
|
result.root += delta;
|
||||||
|
result.other += delta;
|
||||||
|
|
||||||
|
// Modify times if necessary (may further modify dates)
|
||||||
|
const MINUTES_PER_DAY: i64 = 24 * 60;
|
||||||
|
let minutes = delta.num_minutes() % MINUTES_PER_DAY; // May be negative
|
||||||
|
if let Some(times) = self.times {
|
||||||
|
let (root_days, root) = times.root.add_minutes(minutes);
|
||||||
|
let (other_days, other) = times.other.add_minutes(minutes);
|
||||||
|
result.root += Duration::days(root_days);
|
||||||
|
result.other += Duration::days(other_days);
|
||||||
|
result.times = Some(Times { root, other });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -258,8 +258,8 @@ impl DeltaEval {
|
||||||
None => return Err(self.err_time(span)),
|
None => return Err(self.err_time(span)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let (days, time) = time.add_hours(amount);
|
let (days, time) = time.add_hours(amount.into());
|
||||||
self.curr += Duration::days(days.into());
|
self.curr += Duration::days(days);
|
||||||
self.curr_time = Some(time);
|
self.curr_time = Some(time);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -270,8 +270,8 @@ impl DeltaEval {
|
||||||
None => return Err(self.err_time(span)),
|
None => return Err(self.err_time(span)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let (days, time) = time.add_minutes(amount);
|
let (days, time) = time.add_minutes(amount.into());
|
||||||
self.curr += Duration::days(days.into());
|
self.curr += Duration::days(days);
|
||||||
self.curr_time = Some(time);
|
self.curr_time = Some(time);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -265,7 +265,8 @@ pub enum Statement {
|
||||||
Move {
|
Move {
|
||||||
span: Span,
|
span: Span,
|
||||||
from: NaiveDate,
|
from: NaiveDate,
|
||||||
to: NaiveDate,
|
to: Option<NaiveDate>,
|
||||||
|
to_time: Option<Time>,
|
||||||
},
|
},
|
||||||
Remind(Option<Spanned<Delta>>),
|
Remind(Option<Spanned<Delta>>),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -220,7 +220,14 @@ impl fmt::Display for Statement {
|
||||||
Statement::Until(Some(date)) => writeln!(f, "UNTIL {}", date),
|
Statement::Until(Some(date)) => writeln!(f, "UNTIL {}", date),
|
||||||
Statement::Until(None) => writeln!(f, "UNTIL *"),
|
Statement::Until(None) => writeln!(f, "UNTIL *"),
|
||||||
Statement::Except(date) => writeln!(f, "EXCEPT {}", date),
|
Statement::Except(date) => writeln!(f, "EXCEPT {}", date),
|
||||||
Statement::Move { from, to, .. } => writeln!(f, "MOVE {} TO {}", from, to),
|
Statement::Move {
|
||||||
|
from, to, to_time, ..
|
||||||
|
} => match (to, to_time) {
|
||||||
|
(None, None) => unreachable!(),
|
||||||
|
(Some(to), None) => writeln!(f, "MOVE {} TO {}", from, to),
|
||||||
|
(None, Some(to_time)) => writeln!(f, "MOVE {} TO {}", from, to_time),
|
||||||
|
(Some(to), Some(to_time)) => writeln!(f, "MOVE {} TO {} {}", from, to, to_time),
|
||||||
|
},
|
||||||
Statement::Remind(Some(delta)) => writeln!(f, "REMIND {}", delta),
|
Statement::Remind(Some(delta)) => writeln!(f, "REMIND {}", delta),
|
||||||
Statement::Remind(None) => writeln!(f, "REMIND *"),
|
Statement::Remind(None) => writeln!(f, "REMIND *"),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,7 @@ stmt_bdate = !{ "BDATE" ~ bdatum ~ eol }
|
||||||
stmt_from = !{ "FROM" ~ (datum | "*") ~ eol }
|
stmt_from = !{ "FROM" ~ (datum | "*") ~ eol }
|
||||||
stmt_until = !{ "UNTIL" ~ (datum | "*") ~ eol }
|
stmt_until = !{ "UNTIL" ~ (datum | "*") ~ eol }
|
||||||
stmt_except = !{ "EXCEPT" ~ datum ~ eol }
|
stmt_except = !{ "EXCEPT" ~ datum ~ eol }
|
||||||
stmt_move = !{ "MOVE" ~ datum ~ "TO" ~ datum ~ eol }
|
stmt_move = !{ "MOVE" ~ datum ~ "TO" ~ (datum ~ time? | time) ~ eol }
|
||||||
stmt_remind = !{ "REMIND" ~ (delta | "*") ~ eol }
|
stmt_remind = !{ "REMIND" ~ (delta | "*") ~ eol }
|
||||||
|
|
||||||
statements = { (stmt_date | stmt_bdate | stmt_from | stmt_until | stmt_except | stmt_move | stmt_remind)* }
|
statements = { (stmt_date | stmt_bdate | stmt_from | stmt_until | stmt_except | stmt_move | stmt_remind)* }
|
||||||
|
|
|
||||||
|
|
@ -618,9 +618,23 @@ fn parse_stmt_move(p: Pair<'_, Rule>) -> Result<Statement> {
|
||||||
let span = (&p.as_span()).into();
|
let span = (&p.as_span()).into();
|
||||||
let mut p = p.into_inner();
|
let mut p = p.into_inner();
|
||||||
let from = parse_datum(p.next().unwrap())?.value;
|
let from = parse_datum(p.next().unwrap())?.value;
|
||||||
let to = parse_datum(p.next().unwrap())?.value;
|
|
||||||
assert_eq!(p.next(), None);
|
let mut to = None;
|
||||||
Ok(Statement::Move { span, from, to })
|
let mut to_time = None;
|
||||||
|
for p in p {
|
||||||
|
match p.as_rule() {
|
||||||
|
Rule::datum => to = Some(parse_datum(p)?.value),
|
||||||
|
Rule::time => to_time = Some(parse_time(p)?.value),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Statement::Move {
|
||||||
|
span,
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
to_time,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_stmt_remind(p: Pair<'_, Rule>) -> Result<Statement> {
|
fn parse_stmt_remind(p: Pair<'_, Rule>) -> Result<Statement> {
|
||||||
|
|
|
||||||
|
|
@ -98,10 +98,15 @@ impl Time {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_minutes(&self, amount: i32) -> (i32, Self) {
|
/// How many minutes into the day this time is.
|
||||||
|
fn minutes(&self) -> i64 {
|
||||||
|
(self.hour as i64) * 60 + (self.min as i64)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_minutes(&self, amount: i64) -> (i64, Self) {
|
||||||
match amount.cmp(&0) {
|
match amount.cmp(&0) {
|
||||||
Ordering::Less => {
|
Ordering::Less => {
|
||||||
let mut mins = (self.hour as i32) * 60 + (self.min as i32) + amount;
|
let mut mins = self.minutes() + amount;
|
||||||
|
|
||||||
let days = mins.div_euclid(60 * 24);
|
let days = mins.div_euclid(60 * 24);
|
||||||
mins = mins.rem_euclid(60 * 24);
|
mins = mins.rem_euclid(60 * 24);
|
||||||
|
|
@ -111,7 +116,7 @@ impl Time {
|
||||||
(days, Self::new(hour, min))
|
(days, Self::new(hour, min))
|
||||||
}
|
}
|
||||||
Ordering::Greater => {
|
Ordering::Greater => {
|
||||||
let mut mins = (self.hour as i32) * 60 + (self.min as i32) + amount;
|
let mut mins = self.minutes() + amount;
|
||||||
|
|
||||||
let mut days = mins.div_euclid(60 * 24);
|
let mut days = mins.div_euclid(60 * 24);
|
||||||
mins = mins.rem_euclid(60 * 24);
|
mins = mins.rem_euclid(60 * 24);
|
||||||
|
|
@ -130,9 +135,18 @@ impl Time {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_hours(&self, amount: i32) -> (i32, Self) {
|
pub fn add_hours(&self, amount: i64) -> (i64, Self) {
|
||||||
self.add_minutes(amount * 60)
|
self.add_minutes(amount * 60)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `a.minutes_to(b)` returns the minutes from `a` to `b`, meaning it is
|
||||||
|
/// greater than 0 if `a` is earlier than `b`.
|
||||||
|
///
|
||||||
|
/// May return weird amounts if [`Self::in_normal_range`] is not true for
|
||||||
|
/// both.
|
||||||
|
pub fn minutes_to(&self, other: Self) -> i64 {
|
||||||
|
other.minutes() - self.minutes()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue