use std::path::{Path, PathBuf};
use anyhow::{bail, Context, Result};
use crate::annotations::{HeldLink, HeldNote, Index, Link, Note, Ref};
pub fn writing_root(cwd: &Path) -> Result<PathBuf> {
crate::roots::find_worktree_root(cwd).with_context(|| {
format!(
"{} is in no worktree wsb manages, so there is no project to write into; run this inside one",
cwd.display()
)
})
}
#[derive(Debug, Default, Clone)]
pub struct Targets {
pub refs: Vec<String>,
pub files: Vec<PathBuf>,
pub worktree: bool,
pub project: bool,
}
impl Targets {
pub fn resolve(&self, root: &Path) -> Result<Vec<Ref>> {
let mut refs = Vec::new();
for text in &self.refs {
refs.push(text.parse::<Ref>()?);
}
for file in &self.files {
refs.push(Ref::path_in(root, file)?);
}
if self.worktree {
refs.push(Ref::worktree_of(root)?);
}
if self.project {
refs.push(Ref::project_of(root)?);
}
Ok(refs)
}
pub fn is_empty(&self) -> bool {
self.refs.is_empty() && self.files.is_empty() && !self.worktree && !self.project
}
}
pub fn note_add(cwd: &Path, title: &str, body: &str, about: &Targets, machine: &str) -> Result<(Note, Vec<PathBuf>)> {
let root = writing_root(cwd)?;
let targets = about.resolve(&root)?;
let note = Note::new(title, body, machine)?;
let mut written = vec![note.write(&root)?];
for target in targets {
written.push(Link::new(Ref::Note { id: note.id.clone() }, target, None)?.write(&root)?);
}
Ok((note, written))
}
pub fn note_edit(cwd: &Path, id: &str, title: Option<&str>, body: Option<&str>) -> Result<(Note, PathBuf)> {
if title.is_none() && body.is_none() {
bail!("say what to change: --title, --body or --body-file");
}
let root = writing_root(cwd)?;
let mut note = Note::read(&root, id)?
.with_context(|| format!("this worktree ({}) holds no note {id}", root.display()))?;
if let Some(title) = title {
note.title = title.to_string();
}
if let Some(body) = body {
note.body = body.to_string();
}
let path = note.write(&root)?;
Ok((note, path))
}
pub fn note_rm(cwd: &Path, id: &str) -> Result<Vec<PathBuf>> {
let root = writing_root(cwd)?;
let mut removed = vec![crate::annotations::remove_note(&root, id)?];
let note = Ref::Note { id: id.to_string() };
for link in Link::all(&root)? {
if link.other_end(¬e).is_some() {
removed.push(crate::annotations::remove_link(&root, &link)?);
}
}
Ok(removed)
}
pub fn link_add(cwd: &Path, from: &Ref, to: &Ref, label: Option<&str>) -> Result<PathBuf> {
let root = writing_root(cwd)?;
Link::new(from.clone(), to.clone(), label.map(str::to_string))?.write(&root)
}
pub fn link_rm(cwd: &Path, from: &Ref, to: &Ref, label: Option<&str>) -> Result<PathBuf> {
let root = writing_root(cwd)?;
crate::annotations::remove_link(&root, &Link::new(from.clone(), to.clone(), label.map(str::to_string))?)
}
pub fn index() -> Result<Index> {
let workshop = wsb_prj::Workshop::from_env()?;
Index::gather(&workshop)
}
pub fn notes_about<'a>(index: &'a Index, about: Option<&Ref>) -> Vec<&'a HeldNote> {
match about {
None => index.notes.iter().collect(),
Some(about) => index
.notes
.iter()
.filter(|held| {
let note = Ref::Note { id: held.note.id.clone() };
index.links.iter().any(|link| link.link.other_end(¬e) == Some(about))
})
.collect(),
}
}
pub fn links_at<'a>(index: &'a Index, at: Option<&'a Ref>) -> Vec<&'a HeldLink> {
match at {
None => index.links.iter().collect(),
Some(at) => index.links_at(at).collect(),
}
}
pub fn migrate(cwd: &Path) -> Result<crate::migrate::Report> {
let workshop = match wsb_prj::Workshop::from_env() {
Ok(workshop) => Some(workshop),
Err(wsb_prj::WorkshopError::NotSet) => None,
Err(other) => return Err(other.into()),
};
let mut report = match &workshop {
Some(workshop) => crate::migrate::migrate_workshop(workshop)?,
None => crate::migrate::migrate_machine(None)?,
};
if let Some(root) = crate::roots::find_super_root(cwd) {
if workshop.as_ref().is_none_or(|workshop| workshop.locate(&root).is_err()) {
let outside = crate::migrate::migrate_outside(&root)?;
report.done.extend(outside.done);
report.refused.extend(outside.refused);
}
}
Ok(report)
}
#[cfg(test)]
#[path = "annotations_tests.rs"]
mod tests;