/ radicle-cli / src / commands / unassign.rs
unassign.rs
 1  use std::ffi::OsString;
 2  
 3  use anyhow::anyhow;
 4  use nonempty::NonEmpty;
 5  
 6  use radicle::cob;
 7  use radicle::cob::issue;
 8  use radicle::prelude::Did;
 9  use radicle::storage::WriteStorage;
10  
11  use crate::terminal as term;
12  use crate::terminal::args::{Args, Error, Help};
13  
14  pub const HELP: Help = Help {
15      name: "unassign",
16      description: "Unassign an issue",
17      version: env!("CARGO_PKG_VERSION"),
18      usage: r#"
19  Usage
20  
21      rad unassign <issue-id> --from <did> [<option>...]
22  
23      To unassign multiple users from an issue, you may repeat
24      the `--from` option.
25  
26      --from <did>     Assignee to remove from the issue
27  
28  Options
29  
30      --help           Print help
31  "#,
32  };
33  
34  #[derive(Debug)]
35  pub struct Options {
36      pub id: issue::IssueId,
37      pub from: NonEmpty<Did>,
38  }
39  
40  impl Args for Options {
41      fn from_args(args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> {
42          use lexopt::prelude::*;
43  
44          let mut parser = lexopt::Parser::from_args(args);
45          let mut id: Option<issue::IssueId> = None;
46          let mut from: Vec<Did> = Vec::new();
47  
48          while let Some(arg) = parser.next()? {
49              match arg {
50                  Long("help") | Short('h') => {
51                      return Err(Error::Help.into());
52                  }
53                  Long("from") => {
54                      let val = parser.value()?;
55                      let did = term::args::did(&val)?;
56  
57                      from.push(did);
58                  }
59                  Value(ref val) if id.is_none() => {
60                      id = Some(term::args::issue(val)?);
61                  }
62                  _ => {
63                      return Err(anyhow!(arg.unexpected()));
64                  }
65              }
66          }
67  
68          Ok((
69              Options {
70                  id: id.ok_or_else(|| anyhow!("an issue must be specified"))?,
71                  from: NonEmpty::from_vec(from)
72                      .ok_or_else(|| anyhow!("an assignee must be specified"))?,
73              },
74              vec![],
75          ))
76      }
77  }
78  
79  pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
80      let profile = ctx.profile()?;
81      let (_, id) = radicle::rad::cwd()?;
82      let repo = profile.storage.repository_mut(id)?;
83      let mut issues = issue::Issues::open(&repo)?;
84      let mut issue = issues.get_mut(&options.id).map_err(|e| match e {
85          cob::store::Error::NotFound(_, _) => anyhow!("issue {} not found", options.id),
86          _ => e.into(),
87      })?;
88      let signer = term::signer(&profile)?;
89      let assigned = issue
90          .assigned()
91          .cloned()
92          .filter(|did| !options.from.contains(did))
93          .collect::<Vec<_>>();
94  
95      issue.assign(assigned, &signer)?;
96  
97      Ok(())
98  }