use crate::commands::{expand_ids, for_each_id, maybe_sync, touch};
use crate::error::{usage, OpysError, Result};
use crate::frontmatter::Frontmatter;
use crate::project::Project;
use crate::refs;
use crate::store::Store;
use crate::Ctx;
pub fn block_core(prj: &Project, store: &mut Store, id: &str, by: &str) -> Result<()> {
if id == by {
return Err(usage("an item cannot block itself"));
}
let id_title = store
.title_of(id)?
.ok_or_else(|| OpysError::NotFound { id: id.to_string() })?;
let by_title = store
.title_of(by)?
.ok_or_else(|| OpysError::NotFound { id: by.to_string() })?;
let id_blockable = has_status(prj, id, "blocked");
edit_doc(prj, store, id, true, |fm| {
let mut changed = refs::add_to_map(fm, refs::BLOCKED_BY, by, &by_title);
if id_blockable && fm.status() != Some("blocked") {
fm.set_str("status", "blocked");
changed = true;
}
if changed {
touch(fm);
}
changed
})?;
edit_doc(prj, store, by, false, |fm| {
refs::add_to_map(fm, refs::BLOCKS, id, &id_title)
})?;
Ok(())
}
pub fn block(ctx: &Ctx, ids: &str, by: &str) -> Result<()> {
let prj = ctx.open()?;
let ids = expand_ids(ids)?;
let (mut store, _) = ctx.load(&prj)?;
let res = for_each_id(&ids, |id| {
block_core(&prj, &mut store, id, by)?;
println!("{id} blocked by {by}");
Ok(())
});
ctx.flush(&prj, store)?;
maybe_sync(ctx, &prj);
res
}
pub fn unblock_core(prj: &Project, store: &mut Store, id: &str, by: &str) -> Result<()> {
if store.dkey_opt(id)?.is_none() {
return Err(OpysError::NotFound { id: id.to_string() });
}
let linked = store.has_relation(id, refs::BLOCKED_BY, by)?
|| store.has_relation(by, refs::BLOCKS, id)?;
if !linked {
return Err(usage(format!("no blocker '{by}' recorded on '{id}'")));
}
let id_blockable = has_status(prj, id, "blocked") && has_status(prj, id, "in-progress");
edit_doc(prj, store, id, true, |fm| {
let mut changed = refs::remove_from_map(fm, refs::BLOCKED_BY, by);
if id_blockable
&& fm.status() == Some("blocked")
&& refs::parse_in(fm, refs::BLOCKED_BY).is_empty()
&& fm.get_str("blocked_reason").is_none()
{
fm.set_str("status", "in-progress");
changed = true;
}
if changed {
touch(fm);
}
changed
})?;
match edit_doc(prj, store, by, false, |fm| {
refs::remove_from_map(fm, refs::BLOCKS, id)
}) {
Ok(()) | Err(OpysError::NotFound { .. }) => {}
Err(e) => return Err(e),
}
Ok(())
}
pub fn unblock(ctx: &Ctx, ids: &str, by: &str) -> Result<()> {
let prj = ctx.open()?;
let ids = expand_ids(ids)?;
let (mut store, _) = ctx.load(&prj)?;
let res = for_each_id(&ids, |id| {
unblock_core(&prj, &mut store, id, by)?;
println!("{id} no longer blocked by {by}");
Ok(())
});
ctx.flush(&prj, store)?;
maybe_sync(ctx, &prj);
res
}
fn has_status(prj: &Project, id: &str, status: &str) -> bool {
prj.pcfg
.type_name_for_id(id)
.map(|n| prj.pcfg.types[n].statuses.iter().any(|s| s == status))
.unwrap_or(false)
}
fn edit_doc<F>(prj: &Project, store: &mut Store, id: &str, relocate: bool, f: F) -> Result<()>
where
F: FnOnce(&mut Frontmatter) -> bool,
{
let dkey = store.dkey_of(id)?;
let mut doc = store.doc(dkey)?;
if f(&mut doc.frontmatter) {
store.put_doc(&prj.pcfg, Some(dkey), &doc)?;
if relocate {
store.set_canonical_path(&prj.pcfg, dkey)?;
}
}
Ok(())
}