cli: unify label and unlabel
Make the CLI easier by unifying the label and unlabel, using `--add` and `--remove` options instead. Signed-off-by: Fintan Halpenny <fintan.halpenny@gmail.com> X-Clacks-Overhead: GNU Terry Pratchett
This commit is contained in:
parent
d32a10b4b9
commit
67cbf15e45
|
|
@ -52,7 +52,7 @@ duplicated. While we're at it, let's add a label.
|
|||
|
||||
```
|
||||
$ rad issue assign d185ee1 --add did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
||||
$ rad issue label d185ee1 -l good-first-issue
|
||||
$ rad issue label d185ee1 --add good-first-issue
|
||||
```
|
||||
|
||||
It will now show in the list of issues assigned to us, along with the new label.
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ We can also label patches as well as assign DIDs to the patch to help
|
|||
organise your workflow:
|
||||
|
||||
```
|
||||
$ rad patch label 6ff4f09 -l fun
|
||||
$ rad patch label 6ff4f09 --add fun
|
||||
$ rad patch assign 6ff4f09 --add did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
||||
$ rad patch show 6ff4f09
|
||||
╭────────────────────────────────────────────────────╮
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ use std::str::FromStr;
|
|||
|
||||
use anyhow::{anyhow, Context as _};
|
||||
|
||||
use nonempty::NonEmpty;
|
||||
use radicle::cob::common::{Label, Reaction};
|
||||
use radicle::cob::issue;
|
||||
use radicle::cob::issue::{CloseReason, Issues, State};
|
||||
|
|
@ -40,7 +39,7 @@ Usage
|
|||
rad issue open [--title <title>] [--description <text>] [--label <label>] [<option>...]
|
||||
rad issue react <issue-id> [--emoji <char>] [--to <comment>] [<option>...]
|
||||
rad issue assign <issue-id> [--add <did>] [--remove <did>] [<option>...]
|
||||
rad issue label <issue-id> --label <label> [<option>...]
|
||||
rad issue label <issue-id> [--add <label>] [--remove <did>] [<option>...]
|
||||
rad issue unlabel <issue-id> --label <label> [<option>...]
|
||||
rad issue comment <issue-id> [--message <message>] [--reply-to <comment-id>] [<option>...]
|
||||
rad issue show <issue-id> [<option>...]
|
||||
|
|
@ -56,11 +55,11 @@ Assign options
|
|||
|
||||
Label options
|
||||
|
||||
-l, --label Label the issue with the provided label (may be specified multiple times)
|
||||
-a, --add <label> Add a label to the issue (may be specified multiple times).
|
||||
Note: --add will take precedence over --remove
|
||||
|
||||
Unlabel options
|
||||
|
||||
-l, --label Remove the provided label from the issue (may be specified multiple times)
|
||||
-r, --remove <label> Remove a label from the issue (may be specified multiple times).
|
||||
Note: --add will take precedence over --remove
|
||||
|
||||
Options
|
||||
|
||||
|
|
@ -84,7 +83,6 @@ pub enum OperationName {
|
|||
React,
|
||||
Show,
|
||||
State,
|
||||
Unlabel,
|
||||
}
|
||||
|
||||
/// Command line Peer argument.
|
||||
|
|
@ -135,11 +133,7 @@ pub enum Operation {
|
|||
},
|
||||
Label {
|
||||
id: Rev,
|
||||
labels: NonEmpty<Label>,
|
||||
},
|
||||
Unlabel {
|
||||
id: Rev,
|
||||
labels: NonEmpty<Label>,
|
||||
opts: LabelOptions,
|
||||
},
|
||||
List {
|
||||
assigned: Option<Assigned>,
|
||||
|
|
@ -153,6 +147,12 @@ pub struct AssignOptions {
|
|||
pub remove: BTreeSet<Did>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
pub struct LabelOptions {
|
||||
pub add: BTreeSet<Label>,
|
||||
pub remove: BTreeSet<Label>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Options {
|
||||
pub op: Operation,
|
||||
|
|
@ -181,6 +181,7 @@ impl Args for Options {
|
|||
let mut announce = true;
|
||||
let mut quiet = false;
|
||||
let mut assign_opts = AssignOptions::default();
|
||||
let mut label_opts = LabelOptions::default();
|
||||
|
||||
while let Some(arg) = parser.next()? {
|
||||
match arg {
|
||||
|
|
@ -206,12 +207,7 @@ impl Args for Options {
|
|||
Long("title") if op == Some(OperationName::Open) => {
|
||||
title = Some(parser.value()?.to_string_lossy().into());
|
||||
}
|
||||
Short('l') | Long("label")
|
||||
if matches!(
|
||||
op,
|
||||
Some(OperationName::Open | OperationName::Label | OperationName::Unlabel)
|
||||
) =>
|
||||
{
|
||||
Short('l') | Long("label") if matches!(op, Some(OperationName::Open)) => {
|
||||
let val = parser.value()?;
|
||||
let name = term::args::string(&val);
|
||||
let label = Label::new(name)?;
|
||||
|
|
@ -294,6 +290,22 @@ impl Args for Options {
|
|||
.insert(term::args::did(&parser.value()?)?);
|
||||
}
|
||||
|
||||
// Label options
|
||||
Short('a') | Long("add") if matches!(op, Some(OperationName::Label)) => {
|
||||
let val = parser.value()?;
|
||||
let name = term::args::string(&val);
|
||||
let label = Label::new(name)?;
|
||||
|
||||
label_opts.add.insert(label);
|
||||
}
|
||||
Short('r') | Long("remove") if matches!(op, Some(OperationName::Label)) => {
|
||||
let val = parser.value()?;
|
||||
let name = term::args::string(&val);
|
||||
let label = Label::new(name)?;
|
||||
|
||||
label_opts.remove.insert(label);
|
||||
}
|
||||
|
||||
Long("quiet") | Short('q') => {
|
||||
quiet = true;
|
||||
}
|
||||
|
|
@ -308,7 +320,6 @@ impl Args for Options {
|
|||
"s" | "state" => op = Some(OperationName::State),
|
||||
"assign" => op = Some(OperationName::Assign),
|
||||
"label" => op = Some(OperationName::Label),
|
||||
"unlabel" => op = Some(OperationName::Unlabel),
|
||||
|
||||
unknown => anyhow::bail!("unknown operation '{}'", unknown),
|
||||
},
|
||||
|
|
@ -361,13 +372,7 @@ impl Args for Options {
|
|||
},
|
||||
OperationName::Label => Operation::Label {
|
||||
id: id.ok_or_else(|| anyhow!("an issue to label must be provided"))?,
|
||||
labels: NonEmpty::from_vec(labels)
|
||||
.ok_or_else(|| anyhow!("at least one label must be specified"))?,
|
||||
},
|
||||
OperationName::Unlabel => Operation::Unlabel {
|
||||
id: id.ok_or_else(|| anyhow!("an issue to label must be provided"))?,
|
||||
labels: NonEmpty::from_vec(labels)
|
||||
.ok_or_else(|| anyhow!("at least one label must be specified"))?,
|
||||
opts: label_opts,
|
||||
},
|
||||
OperationName::List => Operation::List { assigned, state },
|
||||
};
|
||||
|
|
@ -397,7 +402,6 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
| Operation::Delete { .. }
|
||||
| Operation::Assign { .. }
|
||||
| Operation::Label { .. }
|
||||
| Operation::Unlabel { .. }
|
||||
);
|
||||
|
||||
let mut node = Node::new(profile.socket());
|
||||
|
|
@ -501,22 +505,18 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
.collect::<Vec<_>>();
|
||||
issue.assign(assignees, &signer)?;
|
||||
}
|
||||
Operation::Label { id, labels } => {
|
||||
let id = id.resolve(&repo.backend)?;
|
||||
let Ok(mut issue) = issues.get_mut(&id) else {
|
||||
anyhow::bail!("Issue `{id}` not found");
|
||||
};
|
||||
let labels = issue.labels().cloned().chain(labels).collect::<Vec<_>>();
|
||||
issue.label(labels, &signer)?;
|
||||
}
|
||||
Operation::Unlabel { id, labels } => {
|
||||
Operation::Label {
|
||||
id,
|
||||
opts: LabelOptions { add, remove },
|
||||
} => {
|
||||
let id = id.resolve(&repo.backend)?;
|
||||
let Ok(mut issue) = issues.get_mut(&id) else {
|
||||
anyhow::bail!("Issue `{id}` not found");
|
||||
};
|
||||
let labels = issue
|
||||
.labels()
|
||||
.filter(|&l| !labels.contains(l))
|
||||
.filter(|did| !remove.contains(did))
|
||||
.chain(add.iter())
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
issue.label(labels, &signer)?;
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ use std::ffi::OsString;
|
|||
|
||||
use anyhow::anyhow;
|
||||
|
||||
use nonempty::NonEmpty;
|
||||
use radicle::cob::patch::PatchId;
|
||||
use radicle::cob::{patch, Label};
|
||||
use radicle::prelude::*;
|
||||
|
|
@ -57,8 +56,7 @@ Usage
|
|||
rad patch delete <patch-id> [<option>...]
|
||||
rad patch redact <revision-id> [<option>...]
|
||||
rad patch assign <revision-id> [--add <did>] [--remove <did>] [<option>...]
|
||||
rad patch label <revision-id> --label <label> [<option>...]
|
||||
rad patch unlabel <revision-id> --label <label> [<option>...]
|
||||
rad patch label <revision-id> [--add <label>] [--remove <label>] [<option>...]
|
||||
rad patch ready <patch-id> [--undo] [<option>...]
|
||||
rad patch edit <patch-id> [<option>...]
|
||||
rad patch set <patch-id> [<option>...]
|
||||
|
|
@ -87,11 +85,11 @@ Assign options
|
|||
|
||||
Label options
|
||||
|
||||
-l, --label Label the patch with the provided label (may be specified multiple times)
|
||||
-a, --add <label> Add an assignee to the issue (may be specified multiple times).
|
||||
Note: --add will take precedence over --remove
|
||||
|
||||
Unlabel options
|
||||
|
||||
-l, --label Remove the provided label from the patch (may be specified multiple times)
|
||||
-r, --remove <label> Remove an assignee from the issue (may be specified multiple times).
|
||||
Note: --add will take precedence over --remove
|
||||
|
||||
Update options
|
||||
|
||||
|
|
@ -137,7 +135,6 @@ pub enum OperationName {
|
|||
Comment,
|
||||
Ready,
|
||||
Label,
|
||||
Unlabel,
|
||||
#[default]
|
||||
List,
|
||||
Edit,
|
||||
|
|
@ -151,6 +148,12 @@ pub struct AssignOptions {
|
|||
pub remove: BTreeSet<Did>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
pub struct LabelOptions {
|
||||
pub add: BTreeSet<Label>,
|
||||
pub remove: BTreeSet<Label>,
|
||||
}
|
||||
|
||||
pub struct Filter(fn(&patch::State) -> bool);
|
||||
|
||||
impl Filter {
|
||||
|
|
@ -208,11 +211,7 @@ pub enum Operation {
|
|||
},
|
||||
Label {
|
||||
patch_id: Rev,
|
||||
labels: NonEmpty<Label>,
|
||||
},
|
||||
Unlabel {
|
||||
patch_id: Rev,
|
||||
labels: NonEmpty<Label>,
|
||||
opts: LabelOptions,
|
||||
},
|
||||
List {
|
||||
filter: Filter,
|
||||
|
|
@ -260,8 +259,8 @@ impl Args for Options {
|
|||
let mut undo = false;
|
||||
let mut reply_to: Option<Rev> = None;
|
||||
let mut checkout_opts = checkout::Options::default();
|
||||
let mut labels = Vec::new();
|
||||
let mut assign_opts = AssignOptions::default();
|
||||
let mut label_opts = LabelOptions::default();
|
||||
|
||||
while let Some(arg) = parser.next()? {
|
||||
match arg {
|
||||
|
|
@ -343,15 +342,21 @@ impl Args for Options {
|
|||
.insert(term::args::did(&parser.value()?)?);
|
||||
}
|
||||
|
||||
// Un/Label options.
|
||||
Short('l') | Long("label")
|
||||
if matches!(op, Some(OperationName::Label | OperationName::Unlabel)) =>
|
||||
{
|
||||
// Label options.
|
||||
Short('a') | Long("add") if matches!(op, Some(OperationName::Label)) => {
|
||||
let val = parser.value()?;
|
||||
let name = term::args::string(&val);
|
||||
let label = Label::new(name)?;
|
||||
|
||||
labels.push(label);
|
||||
label_opts.add.insert(label);
|
||||
}
|
||||
|
||||
Short('r') | Long("remove") if matches!(op, Some(OperationName::Label)) => {
|
||||
let val = parser.value()?;
|
||||
let name = term::args::string(&val);
|
||||
let label = Label::new(name)?;
|
||||
|
||||
label_opts.remove.insert(label);
|
||||
}
|
||||
|
||||
// List options.
|
||||
|
|
@ -403,7 +408,6 @@ impl Args for Options {
|
|||
"r" | "redact" => op = Some(OperationName::Redact),
|
||||
"assign" => op = Some(OperationName::Assign),
|
||||
"label" => op = Some(OperationName::Label),
|
||||
"unlabel" => op = Some(OperationName::Unlabel),
|
||||
"comment" => op = Some(OperationName::Comment),
|
||||
"set" => op = Some(OperationName::Set),
|
||||
unknown => anyhow::bail!("unknown operation '{}'", unknown),
|
||||
|
|
@ -426,7 +430,6 @@ impl Args for Options {
|
|||
Some(OperationName::Set),
|
||||
Some(OperationName::Assign),
|
||||
Some(OperationName::Label),
|
||||
Some(OperationName::Unlabel),
|
||||
]
|
||||
.contains(&op) =>
|
||||
{
|
||||
|
|
@ -481,13 +484,7 @@ impl Args for Options {
|
|||
},
|
||||
OperationName::Label => Operation::Label {
|
||||
patch_id: patch_id.ok_or_else(|| anyhow!("a patch must be provided"))?,
|
||||
labels: NonEmpty::from_vec(labels)
|
||||
.ok_or_else(|| anyhow!("at least one label must be specified"))?,
|
||||
},
|
||||
OperationName::Unlabel => Operation::Unlabel {
|
||||
patch_id: patch_id.ok_or_else(|| anyhow!("a patch must be provided"))?,
|
||||
labels: NonEmpty::from_vec(labels)
|
||||
.ok_or_else(|| anyhow!("at least one label must be specified"))?,
|
||||
opts: label_opts,
|
||||
},
|
||||
OperationName::Set => Operation::Set {
|
||||
patch_id: patch_id.ok_or_else(|| anyhow!("a patch must be provided"))?,
|
||||
|
|
@ -602,13 +599,12 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
let patch_id = patch_id.resolve(&repository.backend)?;
|
||||
assign::run(&patch_id, add, remove, &profile, &repository)?;
|
||||
}
|
||||
Operation::Label { patch_id, labels } => {
|
||||
Operation::Label {
|
||||
patch_id,
|
||||
opts: LabelOptions { add, remove },
|
||||
} => {
|
||||
let patch_id = patch_id.resolve(&repository.backend)?;
|
||||
label::add(&patch_id, labels, &profile, &repository)?;
|
||||
}
|
||||
Operation::Unlabel { patch_id, labels } => {
|
||||
let patch_id = patch_id.resolve(&repository.backend)?;
|
||||
label::remove(&patch_id, labels, &profile, &repository)?;
|
||||
label::run(&patch_id, add, remove, &profile, &repository)?;
|
||||
}
|
||||
Operation::Set { patch_id } => {
|
||||
let patches = radicle::cob::patch::Patches::open(&repository)?;
|
||||
|
|
|
|||
|
|
@ -1,29 +1,13 @@
|
|||
use super::*;
|
||||
|
||||
use nonempty::NonEmpty;
|
||||
use radicle::storage::git::Repository;
|
||||
|
||||
use crate::terminal as term;
|
||||
|
||||
pub fn add(
|
||||
pub fn run(
|
||||
patch_id: &PatchId,
|
||||
labels: NonEmpty<Label>,
|
||||
profile: &Profile,
|
||||
repository: &Repository,
|
||||
) -> anyhow::Result<()> {
|
||||
let signer = term::signer(profile)?;
|
||||
let mut patches = radicle::cob::patch::Patches::open(repository)?;
|
||||
let Ok(mut patch) = patches.get_mut(patch_id) else {
|
||||
anyhow::bail!("Patch `{patch_id}` not found");
|
||||
};
|
||||
let labels = patch.labels().cloned().chain(labels).collect::<Vec<_>>();
|
||||
patch.label(labels, &signer)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove(
|
||||
patch_id: &PatchId,
|
||||
labels: NonEmpty<Label>,
|
||||
add: BTreeSet<Label>,
|
||||
remove: BTreeSet<Label>,
|
||||
profile: &Profile,
|
||||
repository: &Repository,
|
||||
) -> anyhow::Result<()> {
|
||||
|
|
@ -34,7 +18,8 @@ pub fn remove(
|
|||
};
|
||||
let labels = patch
|
||||
.labels()
|
||||
.filter(|&l| !labels.contains(l))
|
||||
.filter(|l| !remove.contains(l))
|
||||
.chain(add.iter())
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
patch.label(labels, &signer)?;
|
||||
|
|
|
|||
Loading…
Reference in New Issue