Add support for modifying dependencies
This requires "resolving" dependencies after the command-line parsing phase is complete.
This commit is contained in:
@@ -1,12 +1,97 @@
|
||||
use crate::argparse::{DescriptionMod, Modification};
|
||||
use crate::argparse::{DescriptionMod, Modification, TaskId};
|
||||
use std::collections::HashSet;
|
||||
use taskchampion::chrono::Utc;
|
||||
use taskchampion::{Annotation, TaskMut};
|
||||
use taskchampion::{Annotation, Replica, TaskMut};
|
||||
|
||||
/// A wrapper for Modification, promising that all TaskId instances are of variant TaskId::Uuid.
|
||||
pub(super) struct ResolvedModification(pub(super) Modification);
|
||||
|
||||
/// Resolve a Modification to a ResolvedModification, based on access to a Replica.
|
||||
///
|
||||
/// This is not automatically done in `apply_modification` because, by that time, the TaskMut being
|
||||
/// modified has an exclusive reference to the Replica, so it is impossible to search for matching
|
||||
/// tasks.
|
||||
pub(super) fn resolve_modification(
|
||||
unres: Modification,
|
||||
replica: &mut Replica,
|
||||
) -> anyhow::Result<ResolvedModification> {
|
||||
Ok(ResolvedModification(Modification {
|
||||
description: unres.description,
|
||||
status: unres.status,
|
||||
wait: unres.wait,
|
||||
active: unres.active,
|
||||
add_tags: unres.add_tags,
|
||||
remove_tags: unres.remove_tags,
|
||||
add_dependencies: resolve_task_ids(replica, unres.add_dependencies)?,
|
||||
remove_dependencies: resolve_task_ids(replica, unres.remove_dependencies)?,
|
||||
annotate: unres.annotate,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Convert a set of arbitrary TaskId's into TaskIds containing only TaskId::Uuid.
|
||||
fn resolve_task_ids(
|
||||
replica: &mut Replica,
|
||||
task_ids: HashSet<TaskId>,
|
||||
) -> anyhow::Result<HashSet<TaskId>> {
|
||||
// already all UUIDs (or empty)?
|
||||
if task_ids.iter().all(|tid| matches!(tid, TaskId::Uuid(_))) {
|
||||
return Ok(task_ids);
|
||||
}
|
||||
|
||||
let mut result = HashSet::new();
|
||||
let mut working_set = None;
|
||||
let mut all_tasks = None;
|
||||
for tid in task_ids {
|
||||
match tid {
|
||||
TaskId::WorkingSetId(i) => {
|
||||
let ws = match working_set {
|
||||
Some(ref ws) => ws,
|
||||
None => {
|
||||
working_set = Some(replica.working_set()?);
|
||||
working_set.as_ref().unwrap()
|
||||
}
|
||||
};
|
||||
if let Some(u) = ws.by_index(i) {
|
||||
result.insert(TaskId::Uuid(u));
|
||||
}
|
||||
}
|
||||
TaskId::PartialUuid(partial) => {
|
||||
let ts = match all_tasks {
|
||||
Some(ref ts) => ts,
|
||||
None => {
|
||||
all_tasks = Some(
|
||||
replica
|
||||
.all_task_uuids()?
|
||||
.drain(..)
|
||||
.map(|u| (u, u.to_string()))
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
all_tasks.as_ref().unwrap()
|
||||
}
|
||||
};
|
||||
for (u, ustr) in ts {
|
||||
if ustr.starts_with(&partial) {
|
||||
result.insert(TaskId::Uuid(*u));
|
||||
}
|
||||
}
|
||||
}
|
||||
TaskId::Uuid(u) => {
|
||||
result.insert(TaskId::Uuid(u));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Apply the given modification
|
||||
pub(super) fn apply_modification(
|
||||
task: &mut TaskMut,
|
||||
modification: &Modification,
|
||||
modification: &ResolvedModification,
|
||||
) -> anyhow::Result<()> {
|
||||
// unwrap the "Resolved" promise
|
||||
let modification = &modification.0;
|
||||
|
||||
match modification.description {
|
||||
DescriptionMod::Set(ref description) => task.set_description(description.clone())?,
|
||||
DescriptionMod::Prepend(ref description) => {
|
||||
@@ -49,5 +134,114 @@ pub(super) fn apply_modification(
|
||||
})?;
|
||||
}
|
||||
|
||||
for tid in &modification.add_dependencies {
|
||||
if let TaskId::Uuid(u) = tid {
|
||||
task.add_dependency(*u)?;
|
||||
} else {
|
||||
// this Modification is resolved, so all TaskIds should
|
||||
// be the Uuid variant.
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
for tid in &modification.remove_dependencies {
|
||||
if let TaskId::Uuid(u) = tid {
|
||||
task.remove_dependency(*u)?;
|
||||
} else {
|
||||
// this Modification is resolved, so all TaskIds should
|
||||
// be the Uuid variant.
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::invocation::test::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
use taskchampion::{Status, Uuid};
|
||||
|
||||
#[test]
|
||||
fn test_resolve_modifications() {
|
||||
let mut replica = test_replica();
|
||||
let u1 = Uuid::new_v4();
|
||||
let t1 = replica.new_task(Status::Pending, "a task".into()).unwrap();
|
||||
replica.rebuild_working_set(true).unwrap();
|
||||
|
||||
let modi = Modification {
|
||||
add_dependencies: set![TaskId::Uuid(u1), TaskId::WorkingSetId(1)],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let res = resolve_modification(modi, &mut replica).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
res.0.add_dependencies,
|
||||
set![TaskId::Uuid(u1), TaskId::Uuid(t1.get_uuid())],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_task_ids_empty() {
|
||||
let mut replica = test_replica();
|
||||
|
||||
assert_eq!(
|
||||
resolve_task_ids(&mut replica, HashSet::new()).unwrap(),
|
||||
HashSet::new()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_task_ids_all_uuids() {
|
||||
let mut replica = test_replica();
|
||||
let uuid = Uuid::new_v4();
|
||||
let tids = set![TaskId::Uuid(uuid)];
|
||||
assert_eq!(resolve_task_ids(&mut replica, tids.clone()).unwrap(), tids);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_task_ids_working_set_not_found() {
|
||||
let mut replica = test_replica();
|
||||
let tids = set![TaskId::WorkingSetId(13)];
|
||||
assert_eq!(
|
||||
resolve_task_ids(&mut replica, tids.clone()).unwrap(),
|
||||
HashSet::new()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_task_ids_working_set() {
|
||||
let mut replica = test_replica();
|
||||
let t1 = replica.new_task(Status::Pending, "a task".into()).unwrap();
|
||||
let t2 = replica
|
||||
.new_task(Status::Pending, "another task".into())
|
||||
.unwrap();
|
||||
replica.rebuild_working_set(true).unwrap();
|
||||
let tids = set![TaskId::WorkingSetId(1), TaskId::WorkingSetId(2)];
|
||||
let resolved = set![TaskId::Uuid(t1.get_uuid()), TaskId::Uuid(t2.get_uuid())];
|
||||
assert_eq!(resolve_task_ids(&mut replica, tids).unwrap(), resolved);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_task_ids_partial_not_found() {
|
||||
let mut replica = test_replica();
|
||||
let tids = set![TaskId::PartialUuid("abcd".into())];
|
||||
assert_eq!(
|
||||
resolve_task_ids(&mut replica, tids.clone()).unwrap(),
|
||||
HashSet::new()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_task_ids_partial() {
|
||||
let mut replica = test_replica();
|
||||
let t1 = replica.new_task(Status::Pending, "a task".into()).unwrap();
|
||||
let uuid_str = t1.get_uuid().to_string();
|
||||
let tids = set![TaskId::PartialUuid(uuid_str[..8].into())];
|
||||
let resolved = set![TaskId::Uuid(t1.get_uuid())];
|
||||
assert_eq!(resolve_task_ids(&mut replica, tids).unwrap(), resolved);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user