rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 heartwood2b5bc4fca684ce54818bdb39d46a24d4b9685fe6
{
"request": "trigger",
"version": 1,
"event_type": "patch",
"repository": {
"id": "rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5",
"name": "heartwood",
"description": "Radicle Heartwood Protocol & Stack",
"private": false,
"default_branch": "master",
"delegates": [
"did:key:z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT",
"did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz"
]
},
"action": "Created",
"patch": {
"id": "1d2d3910fe72427f1c4f4d52a20f2dc4f3d5cb02",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"title": "cli: A PoC for using clap as CLI args parser",
"state": {
"status": "archived",
"conflicts": []
},
"before": "d56d619f5601ba456c7bb517be96a1418743cf9e",
"after": "2b5bc4fca684ce54818bdb39d46a24d4b9685fe6",
"commits": [
"2b5bc4fca684ce54818bdb39d46a24d4b9685fe6",
"b3276f6af53b1dcf5acf8e2916a24692b914f75a",
"c868ed365b29e400ac565b3441c7ceeda0483b1a",
"5eea81986605a4a6ff718d0ce50900f2c84d3218",
"5abd06e2bf83527f72cbebdb043105cac486f879",
"b3a4d4c3c74d670330b013dcdf8619a0f0e4e721",
"4a1c5fb55929c243dcafb86dac4aa0f04c7e4850",
"86031fdc787793eb45d6440ef876c015dd629421",
"430f945777ddb4e2f692856cd5af074c754d433a",
"2437099c4ead9b22abbc344838fb964a907c737a"
],
"target": "d285eccb23f3986bfa88fc1af033e6dcb0dc3a65",
"labels": [],
"assignees": [],
"revisions": [
{
"id": "1d2d3910fe72427f1c4f4d52a20f2dc4f3d5cb02",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"description": "Add clap for parsing cli args\n\n\nRefactored the issue command using clap\n\n\nSet clap help_template\n\n\ncli: issue command shouldn't ask for passphrase on read-only operations\n\n\ncli: remove cli.rs and set default value for issue list --assigned\n\n\ncli: Add completion stubs for bash/zsh/fish\n\n\nPoint clap fork dep to github.com/icetan instead of local path\n\n\nBump nixpkgs to support latest version of clap\n\n\nPost rebase fixes",
"base": "54aacc96197a48b79fcc260f94312d824f5e0a34",
"oid": "82fc9ca6a9a82cfe54d2ad5f01225316e823bec2",
"timestamp": 1710518067
},
{
"id": "162f10e636d4ecb7d4cb5020ddeb0619ad7a7afc",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"description": "fixup! Post rebase fixes",
"base": "54aacc96197a48b79fcc260f94312d824f5e0a34",
"oid": "b445f684ce1fab4eb5e771e1d13624c61ec3b22c",
"timestamp": 1710519314
},
{
"id": "0db860f6eea41f923dcfb66be37f2c5f856abf0f",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"description": "Changes:\n- rebased\n- fixed some clippy warnings",
"base": "d56d619f5601ba456c7bb517be96a1418743cf9e",
"oid": "2b5bc4fca684ce54818bdb39d46a24d4b9685fe6",
"timestamp": 1715874132
}
]
}
}
{
"response": "triggered",
"run_id": {
"id": "6e14a461-861c-4eaa-9f4f-26ea3c087f3c"
},
"info_url": "https://cci.rad.levitte.org//6e14a461-861c-4eaa-9f4f-26ea3c087f3c.html"
}
Started at: 2026-09-07 19:34:52.843054+02:00
Commands:
$ rad clone rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 .
✓ Creating checkout in ./...
✓ Remote cloudhead@z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT added
✓ Remote-tracking branch cloudhead@z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT/master created for z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT
✓ Remote fintohaps@z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM added
✓ Remote-tracking branch fintohaps@z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM/master created for z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM
✓ Remote lorenz@z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz added
✓ Remote-tracking branch lorenz@z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz/master created for z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz
✓ Repository successfully cloned under /opt/radcis/ci.rad.levitte.org/cci/state/6e14a461-861c-4eaa-9f4f-26ea3c087f3c/w/
╭────────────────────────────────────╮
│ heartwood │
│ Radicle Heartwood Protocol & Stack │
│ 189 issues · 38 patches │
╰────────────────────────────────────╯
Run `cd ./.` to go to the repository directory.
Exit code: 0
$ rad patch checkout 1d2d3910fe72427f1c4f4d52a20f2dc4f3d5cb02
✓ Switched to branch patch/1d2d391 at revision 162f10e
✓ Branch patch/1d2d391 setup to track rad/patches/1d2d3910fe72427f1c4f4d52a20f2dc4f3d5cb02
Exit code: 0
$ git config advice.detachedHead false
Exit code: 0
$ git checkout 2b5bc4fca684ce54818bdb39d46a24d4b9685fe6
HEAD is now at 2b5bc4fc fixup! Post rebase fixes
Exit code: 0
$ rad patch show 1d2d3910fe72427f1c4f4d52a20f2dc4f3d5cb02 -p
╭─────────────────────────────────────────────────────────────────────────────────╮
│ Title cli: A PoC for using clap as CLI args parser │
│ Patch 1d2d3910fe72427f1c4f4d52a20f2dc4f3d5cb02 │
│ Author z6MkqRS…RLqghpk │
│ Head b445f684ce1fab4eb5e771e1d13624c61ec3b22c │
│ Base 54aacc96197a48b79fcc260f94312d824f5e0a34 │
│ Target master │
│ Branches patch/1d2d391 │
│ Commits ahead 10, behind 1526 │
│ Status archived │
│ │
│ Add clap for parsing cli args │
│ │
│ │
│ Refactored the issue command using clap │
│ │
│ │
│ Set clap help_template │
│ │
│ │
│ cli: issue command shouldn't ask for passphrase on read-only operations │
│ │
│ │
│ cli: remove cli.rs and set default value for issue list --assigned │
│ │
│ │
│ cli: Add completion stubs for bash/zsh/fish │
│ │
│ │
│ Point clap fork dep to github.com/icetan instead of local path │
│ │
│ │
│ Bump nixpkgs to support latest version of clap │
│ │
│ │
│ Post rebase fixes │
├─────────────────────────────────────────────────────────────────────────────────┤
│ b445f68 fixup! Post rebase fixes │
│ 82fc9ca Post rebase fixes │
│ 195bae9 Bump nixpkgs to support latest version of clap │
│ 229d801 Point clap fork dep to github.com/icetan instead of local path │
│ 29a69ba cli: Add completion stubs for bash/zsh/fish │
│ 6986522 cli: remove cli.rs and set default value for issue list --assigned │
│ 06ff636 cli: issue command shouldn't ask for passphrase on read-only operations │
│ 56b64c9 Set clap help_template │
│ ef29498 Refactored the issue command using clap │
│ 92cbaa6 Add clap for parsing cli args │
├─────────────────────────────────────────────────────────────────────────────────┤
│ ● Revision 1d2d391 @ 54aacc9..82fc9ca by z6MkqRS…RLqghpk 2 years ago │
│ ↑ Revision 162f10e @ 54aacc9..b445f68 by z6MkqRS…RLqghpk 2 years ago │
│ ↑ Revision 0db860f @ d56d619..2b5bc4f by fintohaps z6Mkire…SQZ3voM 2 years ago │
╰─────────────────────────────────────────────────────────────────────────────────╯
commit b445f684ce1fab4eb5e771e1d13624c61ec3b22c
Author: Christopher Fredén <me@icetan.org>
Date: Fri Mar 15 17:14:10 2024 +0100
fixup! Post rebase fixes
diff --git a/radicle-cli/src/commands/issue.rs b/radicle-cli/src/commands/issue.rs
index 5fa4df630..0b91d424c 100644
--- a/radicle-cli/src/commands/issue.rs
+++ b/radicle-cli/src/commands/issue.rs
@@ -407,70 +407,6 @@ pub fn get_did_hints<R: ReadRepository + radicle::cob::Store>(input: &str) -> Op
})
}
-// pub fn get_assignee_did_hints(input: &str) -> Option<Vec<String>> {
-// let profile = term::profile().ok()?;
-// let (_, rid) = radicle::rad::cwd().ok()?;
-// let repo = profile.storage.repository_mut(rid).ok()?;
-// let issues = Issues::open(&repo).ok()?;
-
-// let completions = issues
-// .all()
-// .ok()?
-// .flat_map(|issue| {
-// issue.map_or(vec![], |(_, issue)| {
-// issue.assignees().cloned().collect::<Vec<_>>()
-// })
-// })
-// .filter_map(|did| {
-// let did = did.to_human();
-// did.starts_with(input).then(|| String::from(did))
-// })
-// .collect::<Vec<_>>();
-
-// Some(completions)
-// }
-
-// pub fn get_issue_id_hints(input: &str) -> Option<Vec<String>> {
-// let profile = term::profile().ok()?;
-
-// let (_, rid) = radicle::rad::cwd().ok()?;
-// let repo = profile.storage.repository_mut(rid).ok()?;
-// let issues = Issues::open(&repo).ok()?;
-
-// let completions = issues
-// .all()
-// .ok()?
-// .filter_map(|issue| {
-// if let Ok((id, _)) = issue {
-// let id = id.to_string();
-// if id.starts_with(input) {
-// return Some(String::from(id.split_at(8).0));
-// }
-// }
-// None
-// })
-// .collect::<Vec<_>>();
-
-// Some(completions)
-// }
-
-// pub fn get_did_hints(input: &str) -> Option<Vec<String>> {
-// let profile = term::profile().ok()?;
-// let (_, rid) = radicle::rad::cwd().ok()?;
-// let repo = profile.storage.repository_mut(rid).ok()?;
-
-// let ids = repo
-// .remote_ids()
-// .ok()?
-// .filter_map(|id| {
-// let id = id.map(|id| Did::from(id).to_human()).ok()?;
-// id.starts_with(input).then_some(id)
-// })
-// .collect::<Vec<_>>();
-
-// Some(ids)
-// }
-
pub fn run(args: IssueArgs, ctx: impl term::Context) -> anyhow::Result<()> {
let profile = ctx.profile()?;
let rid = if let Some(rid) = args.repo {
commit 82fc9ca6a9a82cfe54d2ad5f01225316e823bec2
Author: Christopher Fredén <me@icetan.org>
Date: Fri Mar 15 16:53:52 2024 +0100
Post rebase fixes
diff --git a/radicle-cli/src/commands/issue.rs b/radicle-cli/src/commands/issue.rs
index d395ce3df..5fa4df630 100644
--- a/radicle-cli/src/commands/issue.rs
+++ b/radicle-cli/src/commands/issue.rs
@@ -496,8 +496,8 @@ pub fn run(args: IssueArgs, ctx: impl term::Context) -> anyhow::Result<()> {
match command {
IssueCommands::Delete { id } => {
- let signer = term::signer(&profile)?;
let id = id.resolve(&repo.backend)?;
+ let signer = term::signer(&profile)?;
issues.remove(&id, &signer)?;
}
IssueCommands::Edit {
@@ -535,13 +535,12 @@ pub fn run(args: IssueArgs, ctx: impl term::Context) -> anyhow::Result<()> {
}
}
IssueCommands::State(state_args) => {
- let signer = term::signer(&profile)?;
let id = state_args.id.resolve(&repo.backend)?;
+ let signer = term::signer(&profile)?;
let mut issue = issues.get_mut(&id)?;
issue.lifecycle(to_state(state_args), &signer)?;
}
IssueCommands::Assign { id, add, delete } => {
- let signer = term::signer(&profile)?;
let id = id.resolve(&repo.backend)?;
let Ok(mut issue) = issues.get_mut(&id) else {
anyhow::bail!("Issue `{id}` not found");
@@ -552,6 +551,7 @@ pub fn run(args: IssueArgs, ctx: impl term::Context) -> anyhow::Result<()> {
.chain(add.iter())
.cloned()
.collect::<Vec<_>>();
+ let signer = term::signer(&profile)?;
issue.assign(assignees, &signer)?;
}
IssueCommands::Comment {
@@ -559,10 +559,10 @@ pub fn run(args: IssueArgs, ctx: impl term::Context) -> anyhow::Result<()> {
message,
reply_to,
} => {
- let signer = term::signer(&profile)?;
let issue_id = id.resolve::<cob::ObjectId>(&repo.backend)?;
let mut issue = issues.get_mut(&issue_id)?;
let (body, reply_to) = prompt_comment(message, reply_to, &issue, &repo)?;
+ let signer = term::signer(&profile)?;
let comment_id = issue.comment(body, reply_to, vec![], &signer)?;
if args.quiet {
@@ -577,19 +577,17 @@ pub fn run(args: IssueArgs, ctx: impl term::Context) -> anyhow::Result<()> {
comment_id,
reaction,
} => {
- let signer = term::signer(&profile)?;
let id = id.resolve(&repo.backend)?;
if let Ok(mut issue) = issues.get_mut(&id) {
- let signer = term::signer(&profile)?;
let comment_id = comment_id.unwrap_or_else(|| {
let (comment_id, _) = term::io::comment_select(&issue).unwrap();
*comment_id
});
+ let signer = term::signer(&profile)?;
issue.react(comment_id, reaction, true, &signer)?;
}
}
IssueCommands::Label { id, add, delete } => {
- let signer = term::signer(&profile)?;
let id = id.resolve(&repo.backend)?;
let Ok(mut issue) = issues.get_mut(&id) else {
anyhow::bail!("Issue `{id}` not found");
@@ -600,6 +598,7 @@ pub fn run(args: IssueArgs, ctx: impl term::Context) -> anyhow::Result<()> {
.chain(add.iter())
.cloned()
.collect::<Vec<_>>();
+ let signer = term::signer(&profile)?;
issue.label(labels, &signer)?;
}
IssueCommands::Open {
diff --git a/radicle-cli/src/main.rs b/radicle-cli/src/main.rs
index 7a1bf9c95..388b14258 100644
--- a/radicle-cli/src/main.rs
+++ b/radicle-cli/src/main.rs
@@ -8,7 +8,7 @@ use clap::builder::Styles;
use clap::{CommandFactory, FromArgMatches as _, Parser, Subcommand};
use clap_complete::dynamic::shells::CompleteCommand;
-use radicle::version;
+use radicle::version::Version;
use radicle_cli::commands::rad_issue;
use radicle_cli::commands::*;
use radicle_cli::terminal as term;
@@ -40,7 +40,7 @@ Usage: {usage}
/// Radicle is a distributed GIT forge.
#[derive(Parser, Debug)]
#[command(name = NAME)]
-#[command(version = VERSION)]
+#[command(version = PKG_VERSION)]
#[command(long_version = LONG_VERSION)]
#[command(help_template = HELP_TEMPLATE)]
#[command(propagate_version = true)]
commit 195bae992f9ac5adc8f5e0554443f0bda2489a48
Author: Christopher Fredén <me@icetan.org>
Date: Fri Mar 15 16:19:10 2024 +0100
Bump nixpkgs to support latest version of clap
diff --git a/flake.lock b/flake.lock
index 9e7bcddec..98fae1778 100644
--- a/flake.lock
+++ b/flake.lock
@@ -56,16 +56,15 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1701961404,
- "narHash": "sha256-ieWHyh6kJtabQYUam/dXXi22MgcgLQvl+f2x/95n+us=",
+ "lastModified": 1710513883,
+ "narHash": "sha256-MKYur1pNCrk8bU7agCpMRi3dCCEbv3GwH3pS0XuM1Ps=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "8fef9eee026f0d95c06b5880ef9c1af0f643aadf",
+ "rev": "bda93c2221bc4185056723795c62e1b4cc661c4b",
"type": "github"
},
"original": {
"owner": "NixOS",
- "ref": "release-23.11",
"repo": "nixpkgs",
"type": "github"
}
diff --git a/flake.nix b/flake.nix
index ace294ee6..7554f56e6 100644
--- a/flake.nix
+++ b/flake.nix
@@ -2,7 +2,7 @@
description = "Radicle";
inputs = {
- nixpkgs.url = "github:NixOS/nixpkgs/release-23.11";
+ nixpkgs.url = "github:NixOS/nixpkgs";
crane = {
url = "github:ipetkov/crane";
commit 229d8019b9f9dc1c3544cd90384bd5d4400d75ca
Author: Christopher Fredén <me@icetan.org>
Date: Tue Mar 12 14:08:45 2024 +0100
Point clap fork dep to github.com/icetan instead of local path
diff --git a/Cargo.lock b/Cargo.lock
index f2ccb8750..8c9b990ad 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -529,6 +529,7 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.1"
+source = "git+https://github.com/icetan/clap.git?branch=propagate-template#362f08485f2d548fb5212e1a0b9c9632bbfb3ec1"
dependencies = [
"clap_builder",
"clap_derive",
@@ -537,6 +538,7 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.1"
+source = "git+https://github.com/icetan/clap.git?branch=propagate-template#362f08485f2d548fb5212e1a0b9c9632bbfb3ec1"
dependencies = [
"anstream",
"anstyle",
@@ -547,6 +549,7 @@ dependencies = [
[[package]]
name = "clap_complete"
version = "4.5.1"
+source = "git+https://github.com/icetan/clap.git?branch=propagate-template#362f08485f2d548fb5212e1a0b9c9632bbfb3ec1"
dependencies = [
"clap",
"clap_lex",
@@ -559,6 +562,7 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.0"
+source = "git+https://github.com/icetan/clap.git?branch=propagate-template#362f08485f2d548fb5212e1a0b9c9632bbfb3ec1"
dependencies = [
"heck",
"proc-macro2",
@@ -569,6 +573,7 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.7.0"
+source = "git+https://github.com/icetan/clap.git?branch=propagate-template#362f08485f2d548fb5212e1a0b9c9632bbfb3ec1"
[[package]]
name = "colorchoice"
diff --git a/radicle-cli/Cargo.toml b/radicle-cli/Cargo.toml
index d6b4b5e09..652fb014e 100644
--- a/radicle-cli/Cargo.toml
+++ b/radicle-cli/Cargo.toml
@@ -45,8 +45,8 @@ tree-sitter-bash = { version = "0.20" }
tree-sitter-go = { version = "0.20.0" }
tree-sitter-md = { version = "0.1.5" }
zeroize = { version = "1.1" }
-clap = { path = "/home/icetan/src/radicle/clap", features = ["derive"] }
-clap_complete = { path = "/home/icetan/src/radicle/clap/clap_complete", features = ["unstable-dynamic"] }
+clap = { git = "https://github.com/icetan/clap.git", branch = "propagate-template", features = ["derive"] }
+clap_complete = { git = "https://github.com/icetan/clap.git", branch = "propagate-template", features = ["unstable-dynamic"] }
[dependencies.radicle]
commit 29a69bad9e9c61b94d18cd8f1a6594edd818a967
Author: Christopher Fredén <me@icetan.org>
Date: Tue Mar 5 10:58:28 2024 +0100
cli: Add completion stubs for bash/zsh/fish
diff --git a/radicle-cli/completion/_rad.zsh b/radicle-cli/completion/_rad.zsh
new file mode 100644
index 000000000..8aefe8399
--- /dev/null
+++ b/radicle-cli/completion/_rad.zsh
@@ -0,0 +1,23 @@
+#compdef rad
+# ZSH suggestion shim for Radicle CLI
+
+# DEBUG:
+# echo "\n\n\nstate:\t'$state'"
+# echo "line:\t'$line'"
+# echo "words:\t$words"
+
+export _CLAP_COMPLETE_INDEX=$((${#words[@]} - 1))
+# export _CLAP_COMPLETE_COMP_TYPE=9
+# export _CLAP_COMPLETE_SPACE=false
+
+local completions=($(rad complete --shell bash -- $words "" 2>/dev/null))
+
+if [ -z "$completions" ]
+then
+ # If the completion list is empty, just continue with filename selection
+ # _arguments '*::arguments: _normal'
+ return
+fi
+
+# This is not a variable assignment, don't remove spaces!
+_values = "${(ps:\v:)completions}"
diff --git a/radicle-cli/completion/bash/_rad_bash_complete.sh b/radicle-cli/completion/bash/_rad_bash_complete.sh
new file mode 100644
index 000000000..303be5a8e
--- /dev/null
+++ b/radicle-cli/completion/bash/_rad_bash_complete.sh
@@ -0,0 +1,19 @@
+
+_clap_complete_rad() {
+ export IFS=$'\013'
+ export _CLAP_COMPLETE_INDEX=${COMP_CWORD}
+ export _CLAP_COMPLETE_COMP_TYPE=${COMP_TYPE}
+ if compopt +o nospace 2> /dev/null; then
+ export _CLAP_COMPLETE_SPACE=false
+ else
+ export _CLAP_COMPLETE_SPACE=true
+ fi
+ COMPREPLY=( $("rad" complete --shell bash -- "${COMP_WORDS[@]}") )
+ if [[ $? != 0 ]]; then
+ unset COMPREPLY
+ elif [[ $SUPPRESS_SPACE == 1 ]] && [[ "${COMPREPLY-}" =~ [=/:]$ ]]; then
+ compopt -o nospace
+ fi
+}
+complete -o nospace -o bashdefault -F _clap_complete_rad rad
+
diff --git a/radicle-cli/completion/fish/_rad b/radicle-cli/completion/fish/_rad
new file mode 100644
index 000000000..6c483eacb
--- /dev/null
+++ b/radicle-cli/completion/fish/_rad
@@ -0,0 +1 @@
+complete -x -c rad -a "("'rad'" complete --shell fish -- (commandline --current-process --tokenize --cut-at-cursor) (commandline --current-token))"
diff --git a/radicle-cli/completion/zsh/_rad b/radicle-cli/completion/zsh/_rad
new file mode 100644
index 000000000..8c139da99
--- /dev/null
+++ b/radicle-cli/completion/zsh/_rad
@@ -0,0 +1,24 @@
+#compdef rad
+# ZSH suggestion shim for Radicle CLI
+
+# DEBUG:
+# echo "\n\n\nstate:\t'$state'"
+# echo "line:\t'$line'"
+# echo "words:\t$words"
+
+export IFS=$'\013'
+export _CLAP_COMPLETE_INDEX=$((${#words[@]} - 1))
+# export _CLAP_COMPLETE_COMP_TYPE=9
+# export _CLAP_COMPLETE_SPACE=false
+
+local completions=($(rad complete --shell bash -- $words "" 2>/dev/null))
+
+if [ -z "$completions" ]
+then
+ # If the completion list is empty, just continue with filename selection
+ # _arguments '*::arguments: _normal'
+ return
+fi
+
+# This is not a variable assignment, don't remove spaces!
+_values = "${(ps:\v:)completions}"
commit 698652265a7a45959d8c2d3c7ddc1303361d3747
Author: Christopher Fredén <me@icetan.org>
Date: Mon Mar 4 08:14:14 2024 +0100
cli: remove cli.rs and set default value for issue list --assigned
diff --git a/radicle-cli/src/cli.rs b/radicle-cli/src/cli.rs
deleted file mode 100644
index 1bf0dea86..000000000
--- a/radicle-cli/src/cli.rs
+++ /dev/null
@@ -1,91 +0,0 @@
-use std::ffi::OsString;
-
-use clap::{Command, FromArgMatches, Subcommand};
-use clap_complete::dynamic::shells::CompleteCommand;
-use radicle::identity::Did;
-use radicle::issue::Issues;
-use radicle::storage::{ReadRepository, ReadStorage};
-
-pub fn get_assignee_did_hints(input: &str) -> Option<Vec<String>> {
- let (_, rid) = radicle::rad::cwd().ok()?;
- radicle::Profile::load()
- .ok()
- .and_then(|profile| profile.storage.repository(rid).ok())
- .and_then(|repo| {
- Issues::open(&repo).ok().and_then(|issues| {
- issues
- .all()
- .map(|issues| {
- issues
- .flat_map(|issue| {
- issue.map_or(vec![], |(_, issue)| {
- issue.assignees().cloned().collect::<Vec<_>>()
- })
- })
- .filter_map(|did| {
- let did = did.to_human();
- did.starts_with(input).then(|| String::from(did))
- })
- .collect::<Vec<_>>()
- })
- .ok()
- })
- })
-}
-
-pub fn get_issue_id_hints(input: &str) -> Option<Vec<String>> {
- let (_, rid) = radicle::rad::cwd().ok()?;
- radicle::Profile::load()
- .ok()
- .and_then(|profile| profile.storage.repository(rid).ok())
- .and_then(|repo| {
- Issues::open(&repo).ok().and_then(|issues| {
- issues
- .all()
- .map(|issues| {
- issues
- .filter_map(|issue| {
- if let Ok((id, _)) = issue {
- let id = id.to_string();
- if id.starts_with(input) {
- return Some(String::from(id.split_at(8).0));
- }
- }
- None
- })
- .collect::<Vec<_>>()
- })
- .ok()
- })
- })
-}
-
-pub fn get_did_hints<R: ReadRepository + radicle::cob::Store>(input: &str) -> Option<Vec<String>> {
- let (_, rid) = radicle::rad::cwd().ok()?;
- radicle::Profile::load()
- .ok()
- .and_then(|profile| profile.storage.repository(rid).ok())
- .and_then(|repo| {
- repo.remote_ids()
- .map(|issues| {
- issues
- .filter_map(|id| {
- let id = id.map(|id| Did::from(id).to_human()).ok()?;
- id.starts_with(input).then_some(id)
- })
- .collect::<Vec<_>>()
- })
- .ok()
- })
-}
-
-pub fn completer(cmd: Command, args: Vec<OsString>) -> () {
- let mut cmd = CompleteCommand::augment_subcommands(cmd);
- let matches = cmd.clone().get_matches();
-
- if let Ok(completions) = CompleteCommand::from_arg_matches(&matches) {
- completions.complete(&mut cmd);
- } else {
- dbg!("COMPLETER FAILED: args: {:?}, matches: {:?}", args, matches);
- }
-}
diff --git a/radicle-cli/src/commands/issue.rs b/radicle-cli/src/commands/issue.rs
index ff3a91582..d395ce3df 100644
--- a/radicle-cli/src/commands/issue.rs
+++ b/radicle-cli/src/commands/issue.rs
@@ -1,6 +1,8 @@
#[path = "issue/cache.rs"]
mod cache;
+use std::str::FromStr;
+
use anyhow::Context as _;
use clap::{ArgGroup, Parser, Subcommand, ValueHint};
@@ -9,15 +11,17 @@ use radicle::cob::issue;
use radicle::cob::issue::{CloseReason, State};
use radicle::cob::thread;
use radicle::crypto::Signer;
+use radicle::identity::did::DidError;
+use radicle::identity::RepoId;
use radicle::issue::cache::Issues as _;
-use radicle::prelude::{Did, RepoId};
+use radicle::issue::Issues;
+use radicle::prelude::Did;
use radicle::profile;
-use radicle::storage;
+use radicle::storage::{self, ReadStorage as _};
use radicle::storage::{ReadRepository, WriteRepository, WriteStorage};
use radicle::Profile;
use radicle::{cob, Node};
-use crate::cli::{get_assignee_did_hints, get_issue_id_hints};
use crate::git::Rev;
use crate::node;
use crate::terminal as term;
@@ -76,7 +80,7 @@ Options
};
/// Command line Peer argument.
-#[derive(Default, Debug, PartialEq, Eq)]
+#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub enum Assigned {
#[default]
Me,
@@ -90,15 +94,18 @@ pub struct IssueArgs {
/// Don't print anything
#[arg(short, long)]
+ #[clap(global = true)]
quiet: bool,
/// Don't announce issue to peers
#[arg(long)]
#[arg(value_name = "no-announce")]
+ #[clap(global = true)]
no_announce: bool,
/// Show only the issue header, hiding the comments
#[arg(long)]
+ #[clap(global = true)]
header: bool,
#[arg(long, short)]
@@ -236,10 +243,13 @@ enum IssueCommands {
#[derive(Parser, Debug)]
struct ListArgs {
- /// List issues assigned to <did>
+ /// List issues assigned to <did> (default: me)
#[clap(value_hint = ValueHint::Dynamic(get_assignee_did_hints))]
#[arg(long, name = "did")]
- assigned: Option<Did>,
+ #[arg(default_missing_value = "me")]
+ #[arg(num_args = 0..=1)]
+ #[arg(require_equals = true)]
+ assigned: Option<Assigned>,
/// List all issues (default)
#[arg(long, group = "state")]
@@ -278,6 +288,18 @@ struct StateArgs {
solved: bool,
}
+impl FromStr for Assigned {
+ type Err = DidError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ if s == "me" {
+ Ok(Assigned::Me)
+ } else {
+ let value = s.parse::<Did>()?;
+ Ok(Assigned::Peer(value))
+ }
+ }
+}
fn to_state_filter(args: ListArgs) -> Option<State> {
if args.open {
@@ -312,6 +334,143 @@ fn to_state(args: StateArgs) -> State {
}
}
+pub fn get_assignee_did_hints(input: &str) -> Option<Vec<String>> {
+ let (_, rid) = radicle::rad::cwd().ok()?;
+ radicle::Profile::load()
+ .ok()
+ .and_then(|profile| profile.storage.repository(rid).ok())
+ .and_then(|repo| {
+ Issues::open(&repo).ok().and_then(|issues| {
+ issues
+ .all()
+ .map(|issues| {
+ issues
+ .flat_map(|issue| {
+ issue.map_or(vec![], |(_, issue)| {
+ issue.assignees().cloned().collect::<Vec<_>>()
+ })
+ })
+ .filter_map(|did| {
+ let did = did.to_human();
+ did.starts_with(input).then(|| String::from(did))
+ })
+ .collect::<Vec<_>>()
+ })
+ .ok()
+ })
+ })
+}
+
+pub fn get_issue_id_hints(input: &str) -> Option<Vec<String>> {
+ let (_, rid) = radicle::rad::cwd().ok()?;
+ radicle::Profile::load()
+ .ok()
+ .and_then(|profile| profile.storage.repository(rid).ok())
+ .and_then(|repo| {
+ Issues::open(&repo).ok().and_then(|issues| {
+ issues
+ .all()
+ .map(|issues| {
+ issues
+ .filter_map(|issue| {
+ if let Ok((id, _)) = issue {
+ let id = id.to_string();
+ if id.starts_with(input) {
+ return Some(String::from(id.split_at(8).0));
+ }
+ }
+ None
+ })
+ .collect::<Vec<_>>()
+ })
+ .ok()
+ })
+ })
+}
+
+pub fn get_did_hints<R: ReadRepository + radicle::cob::Store>(input: &str) -> Option<Vec<String>> {
+ let (_, rid) = radicle::rad::cwd().ok()?;
+ radicle::Profile::load()
+ .ok()
+ .and_then(|profile| profile.storage.repository(rid).ok())
+ .and_then(|repo| {
+ repo.remote_ids()
+ .map(|issues| {
+ issues
+ .filter_map(|id| {
+ let id = id.map(|id| Did::from(id).to_human()).ok()?;
+ id.starts_with(input).then_some(id)
+ })
+ .collect::<Vec<_>>()
+ })
+ .ok()
+ })
+}
+
+// pub fn get_assignee_did_hints(input: &str) -> Option<Vec<String>> {
+// let profile = term::profile().ok()?;
+// let (_, rid) = radicle::rad::cwd().ok()?;
+// let repo = profile.storage.repository_mut(rid).ok()?;
+// let issues = Issues::open(&repo).ok()?;
+
+// let completions = issues
+// .all()
+// .ok()?
+// .flat_map(|issue| {
+// issue.map_or(vec![], |(_, issue)| {
+// issue.assignees().cloned().collect::<Vec<_>>()
+// })
+// })
+// .filter_map(|did| {
+// let did = did.to_human();
+// did.starts_with(input).then(|| String::from(did))
+// })
+// .collect::<Vec<_>>();
+
+// Some(completions)
+// }
+
+// pub fn get_issue_id_hints(input: &str) -> Option<Vec<String>> {
+// let profile = term::profile().ok()?;
+
+// let (_, rid) = radicle::rad::cwd().ok()?;
+// let repo = profile.storage.repository_mut(rid).ok()?;
+// let issues = Issues::open(&repo).ok()?;
+
+// let completions = issues
+// .all()
+// .ok()?
+// .filter_map(|issue| {
+// if let Ok((id, _)) = issue {
+// let id = id.to_string();
+// if id.starts_with(input) {
+// return Some(String::from(id.split_at(8).0));
+// }
+// }
+// None
+// })
+// .collect::<Vec<_>>();
+
+// Some(completions)
+// }
+
+// pub fn get_did_hints(input: &str) -> Option<Vec<String>> {
+// let profile = term::profile().ok()?;
+// let (_, rid) = radicle::rad::cwd().ok()?;
+// let repo = profile.storage.repository_mut(rid).ok()?;
+
+// let ids = repo
+// .remote_ids()
+// .ok()?
+// .filter_map(|id| {
+// let id = id.map(|id| Did::from(id).to_human()).ok()?;
+// id.starts_with(input).then_some(id)
+// })
+// .collect::<Vec<_>>();
+
+// Some(ids)
+// }
+
pub fn run(args: IssueArgs, ctx: impl term::Context) -> anyhow::Result<()> {
let profile = ctx.profile()?;
let rid = if let Some(rid) = args.repo {
@@ -353,7 +512,7 @@ pub fn run(args: IssueArgs, ctx: impl term::Context) -> anyhow::Result<()> {
}
}
IssueCommands::List(list_args) => {
- let assigned = list_args.assigned.map(Assigned::Peer);
+ let assigned = list_args.assigned.clone();
let state = to_state_filter(list_args);
list(issues, &assigned, &state, &profile)?;
}
diff --git a/radicle-cli/src/lib.rs b/radicle-cli/src/lib.rs
index f71f7c9cb..32019151d 100644
--- a/radicle-cli/src/lib.rs
+++ b/radicle-cli/src/lib.rs
@@ -7,4 +7,3 @@ pub mod node;
pub mod pager;
pub mod project;
pub mod terminal;
-pub mod cli;
diff --git a/radicle-cli/src/main.rs b/radicle-cli/src/main.rs
index 23c75957c..7a1bf9c95 100644
--- a/radicle-cli/src/main.rs
+++ b/radicle-cli/src/main.rs
@@ -5,10 +5,10 @@ use std::{io::ErrorKind, iter, process};
use anyhow::anyhow;
use clap::builder::styling::Style;
use clap::builder::Styles;
-use clap::{CommandFactory, Parser, Subcommand};
+use clap::{CommandFactory, FromArgMatches as _, Parser, Subcommand};
+use clap_complete::dynamic::shells::CompleteCommand;
use radicle::version;
-use radicle_cli::cli;
use radicle_cli::commands::rad_issue;
use radicle_cli::commands::*;
use radicle_cli::terminal as term;
@@ -45,7 +45,6 @@ Usage: {usage}
#[command(help_template = HELP_TEMPLATE)]
#[command(propagate_version = true)]
#[command(propagate_help_template = true)]
-// #[command(styles = Styles::styled().usage(AnsiColor::Blue.on_default()))]
#[command(styles = Styles::plain().literal(Style::new().bold()))]
struct CliArgs {
#[command(subcommand)]
@@ -265,6 +264,8 @@ fn run_other(exe: &str, args: &[OsString]) -> Result<(), Option<anyhow::Error>>
);
}
"issue" => {
+ // Use clap instead to parse all CLI args and ignore `args` passed
+ // to `run_other`.
let args_ = CliArgs::parse();
if let Some(command) = args_.command {
match command {
@@ -274,12 +275,11 @@ fn run_other(exe: &str, args: &[OsString]) -> Result<(), Option<anyhow::Error>>
.map_err(|e| anyhow!(e))?,
)?,
}
- // If clap parsed a command short circuit.
- // return Ok(());
}
}
+ // Used for dynamic shell completion (not user facing)
"complete" => {
- cli::completer(CliArgs::command(), args.to_vec());
+ run_completer(CliArgs::command());
}
"ls" => {
term::run_command_args::<rad_ls::Options, _>(rad_ls::HELP, rad_ls::run, args.to_vec());
@@ -393,3 +393,13 @@ fn run_other(exe: &str, args: &[OsString]) -> Result<(), Option<anyhow::Error>>
}
Ok(())
}
+
+/// Output shell completions
+fn run_completer(cmd: clap::Command) -> () {
+ let mut cmd = CompleteCommand::augment_subcommands(cmd);
+ let matches = cmd.clone().get_matches();
+
+ if let Ok(completions) = CompleteCommand::from_arg_matches(&matches) {
+ completions.complete(&mut cmd);
+ }
+}
commit 06ff636332fa846b750fdef56f247fcbf65f092d
Author: Christopher Fredén <me@icetan.org>
Date: Sat Mar 2 21:51:00 2024 +0100
cli: issue command shouldn't ask for passphrase on read-only operations
diff --git a/radicle-cli/src/commands/issue.rs b/radicle-cli/src/commands/issue.rs
index 70b9030d1..ff3a91582 100644
--- a/radicle-cli/src/commands/issue.rs
+++ b/radicle-cli/src/commands/issue.rs
@@ -421,6 +421,7 @@ pub fn run(args: IssueArgs, ctx: impl term::Context) -> anyhow::Result<()> {
let signer = term::signer(&profile)?;
let id = id.resolve(&repo.backend)?;
if let Ok(mut issue) = issues.get_mut(&id) {
+ let signer = term::signer(&profile)?;
let comment_id = comment_id.unwrap_or_else(|| {
let (comment_id, _) = term::io::comment_select(&issue).unwrap();
*comment_id
commit 56b64c9d3959f4ab5a364504b418c9b006479aba
Author: Christopher Fredén <me@icetan.org>
Date: Fri Mar 1 14:56:17 2024 +0100
Set clap help_template
diff --git a/radicle-cli/src/cli.rs b/radicle-cli/src/cli.rs
index 153de9ae5..1bf0dea86 100644
--- a/radicle-cli/src/cli.rs
+++ b/radicle-cli/src/cli.rs
@@ -89,85 +89,3 @@ pub fn completer(cmd: Command, args: Vec<OsString>) -> () {
dbg!("COMPLETER FAILED: args: {:?}, matches: {:?}", args, matches);
}
}
-
-// pub fn to_issue_options() -> anyhow::Result<rad_issue::Options> {
-// let args = CliArgs::parse();
-
-// // Default to List command.
-// // let command = args.command.unwrap_or(Commands::Issue(IssueArgs {
-// // command: Some(IssueCommands::List(ListArgs {
-// // assigned: None,
-// // filter: ListFilter::All,
-// // })),
-// // header: false,
-// // quiet: false,
-// // no_announce: false,
-// // }));
-
-// let options = match args {
-// CliArgs { command } => match command {
-// Some(Commands::Issue(IssueArgs {
-// command: subcommand,
-// quiet,
-// no_announce,
-// header,
-// })) => match subcommand {
-// Some(IssueCommands::List(args)) => Some(rad_issue::Options {
-// op: rad_issue::Operation::List {
-// assigned: args
-// .assigned
-// .map(|did| {
-// let did =
-// term::args::did(&OsString::from(format!("did:key:{did}")))?;
-// Ok::<Option<Assigned>, anyhow::Error>(Some(Assigned::Peer(did)))
-// })
-// .unwrap_or(Ok(None))?,
-// state: match args.filter {
-// ListFilter::All => None,
-// ListFilter::Open => Some(radicle::cob::issue::State::Open),
-// ListFilter::Closed => Some(State::Closed {
-// reason: CloseReason::Other,
-// }),
-// ListFilter::Solved => Some(State::Closed {
-// reason: CloseReason::Solved,
-// }),
-// },
-// },
-// announce: !no_announce,
-// quiet: quiet,
-// }),
-// Some(IssueCommands::Show(args)) => Some(rad_issue::Options {
-// op: rad_issue::Operation::Show {
-// id: Rev::from(args.id),
-// format: if header {
-// term::issue::Format::Header
-// } else {
-// term::issue::Format::Full
-// },
-// debug: args.debug,
-// },
-// announce: !no_announce,
-// quiet: quiet,
-// }),
-// // Default `issue` subcommand is `list`.
-// _ => Some(rad_issue::Options {
-// op: rad_issue::Operation::List {
-// assigned: None,
-// state: None,
-// },
-// announce: false,
-// quiet: false,
-// }),
-// },
-// _ => None,
-// },
-// };
-
-// let Some(option) = options else {
-// CliArgs::command().write_help(&mut std::io::stdout())?;
-// println!();
-// process::exit(0);
-// };
-
-// Ok(option)
-// }
diff --git a/radicle-cli/src/commands/issue.rs b/radicle-cli/src/commands/issue.rs
index e716ce51d..70b9030d1 100644
--- a/radicle-cli/src/commands/issue.rs
+++ b/radicle-cli/src/commands/issue.rs
@@ -2,7 +2,7 @@
mod cache;
use anyhow::Context as _;
-use clap::{Parser, Subcommand, ValueHint};
+use clap::{ArgGroup, Parser, Subcommand, ValueHint};
use radicle::cob::common::{Label, Reaction};
use radicle::cob::issue;
@@ -107,14 +107,14 @@ pub struct IssueArgs {
#[derive(Subcommand, Debug)]
enum IssueCommands {
- /// Delete an issue.
+ /// Delete an issue
Delete {
#[arg(value_name = "issue-id")]
#[clap(value_hint = ValueHint::Dynamic(get_issue_id_hints))]
id: Rev,
},
- /// Edit an issue.
+ /// Edit an issue
Edit {
#[arg(value_name = "issue-id")]
#[clap(value_hint = ValueHint::Dynamic(get_issue_id_hints))]
@@ -127,15 +127,10 @@ enum IssueCommands {
description: Option<String>,
},
- List {
- #[clap(value_hint = ValueHint::Dynamic(get_assignee_did_hints))]
- #[arg(long, name = "did")]
- assigned: Option<Did>,
-
- #[clap(value_enum, default_value_t=StateFilter::All)]
- state: StateFilter,
- },
+ /// List and filter issues
+ List(ListArgs),
+ /// Create a new issue
Open {
#[arg(long, short)]
title: Option<String>,
@@ -150,6 +145,7 @@ enum IssueCommands {
assignees: Vec<Did>,
},
+ /// Add a reaction emoji to an issue or comment
React {
#[arg(value_name = "issue-id")]
#[clap(value_hint = ValueHint::Dynamic(get_issue_id_hints))]
@@ -184,7 +180,7 @@ enum IssueCommands {
delete: Vec<Did>,
},
- /// Update lables on an issue.
+ /// Update labels on an issue
Label {
/// The issue to label.
#[arg(value_name = "issue-id")]
@@ -219,6 +215,7 @@ enum IssueCommands {
reply_to: Option<Rev>,
},
+ /// Show a specific issue
Show {
#[arg(value_name = "issue-id")]
#[clap(value_hint = ValueHint::Dynamic(get_issue_id_hints))]
@@ -229,57 +226,89 @@ enum IssueCommands {
debug: bool,
},
- State {
- #[arg(value_name = "issue-id")]
- #[clap(value_hint = ValueHint::Dynamic(get_issue_id_hints))]
- id: Rev,
-
- #[clap(value_enum)]
- state: StateChoice,
- },
Cache {
#[arg(value_name = "issue-id")]
id: Option<Rev>,
},
+
+ State(StateArgs),
+}
+
+#[derive(Parser, Debug)]
+struct ListArgs {
+ /// List issues assigned to <did>
+ #[clap(value_hint = ValueHint::Dynamic(get_assignee_did_hints))]
+ #[arg(long, name = "did")]
+ assigned: Option<Did>,
+
+ /// List all issues (default)
+ #[arg(long, group = "state")]
+ all: bool,
+
+ /// List only open issues
+ #[arg(long, group = "state")]
+ open: bool,
+
+ /// List only closed issues
+ #[arg(long, group = "state")]
+ closed: bool,
+
+ /// List only solved issues
+ #[arg(long, group = "state")]
+ solved: bool,
}
-#[derive(clap::ValueEnum, Clone, Debug)]
-enum StateFilter {
- All,
- Closed,
- Open,
- Solved,
+#[derive(Parser, Debug)]
+#[clap(group(ArgGroup::new("state").required(true)))]
+struct StateArgs {
+ #[arg(value_name = "issue-id")]
+ #[clap(value_hint = ValueHint::Dynamic(get_issue_id_hints))]
+ id: Rev,
+
+ /// Set issue state to open
+ #[arg(long, short, group = "state")]
+ open: bool,
+
+ /// Set issue state to closed
+ #[arg(long, short, group = "state")]
+ closed: bool,
+
+ /// Set issue state to solved
+ #[arg(long, short, group = "state")]
+ solved: bool,
}
-fn to_state_filter(list_state: StateFilter) -> Option<State> {
- match list_state {
- StateFilter::All => None,
- StateFilter::Open => Some(radicle::cob::issue::State::Open),
- StateFilter::Closed => Some(State::Closed {
+
+fn to_state_filter(args: ListArgs) -> Option<State> {
+ if args.open {
+ Some(radicle::cob::issue::State::Open)
+ } else if args.closed {
+ Some(State::Closed {
reason: CloseReason::Other,
- }),
- StateFilter::Solved => Some(State::Closed {
+ })
+ } else if args.solved {
+ Some(State::Closed {
reason: CloseReason::Solved,
- }),
+ })
+ } else {
+ None
}
}
-#[derive(clap::ValueEnum, Clone, Debug)]
-enum StateChoice {
- Closed,
- Open,
- Solved,
-}
-
-fn to_state_choice(choice: StateChoice) -> State {
- match choice {
- StateChoice::Open => radicle::cob::issue::State::Open,
- StateChoice::Closed => State::Closed {
+fn to_state(args: StateArgs) -> State {
+ if args.open {
+ radicle::cob::issue::State::Open
+ } else if args.closed {
+ State::Closed {
reason: CloseReason::Other,
- },
- StateChoice::Solved => State::Closed {
+ }
+ } else if args.solved {
+ State::Closed {
reason: CloseReason::Solved,
- },
+ }
+ } else {
+ // FIXME:
+ unreachable!("State flag needed");
}
}
@@ -323,9 +352,9 @@ pub fn run(args: IssueArgs, ctx: impl term::Context) -> anyhow::Result<()> {
term::issue::show(&issue, issue.id(), Format::Header, &profile)?;
}
}
- IssueCommands::List { assigned, state } => {
- let assigned = assigned.map(Assigned::Peer);
- let state = to_state_filter(state);
+ IssueCommands::List(list_args) => {
+ let assigned = list_args.assigned.map(Assigned::Peer);
+ let state = to_state_filter(list_args);
list(issues, &assigned, &state, &profile)?;
}
IssueCommands::Show { id, debug } => {
@@ -346,11 +375,11 @@ pub fn run(args: IssueArgs, ctx: impl term::Context) -> anyhow::Result<()> {
term::issue::show(&issue, &id, format, &profile)?;
}
}
- IssueCommands::State { id, state } => {
+ IssueCommands::State(state_args) => {
let signer = term::signer(&profile)?;
- let id = id.resolve(&repo.backend)?;
+ let id = state_args.id.resolve(&repo.backend)?;
let mut issue = issues.get_mut(&id)?;
- issue.lifecycle(to_state_choice(state), &signer)?;
+ issue.lifecycle(to_state(state_args), &signer)?;
}
IssueCommands::Assign { id, add, delete } => {
let signer = term::signer(&profile)?;
diff --git a/radicle-cli/src/main.rs b/radicle-cli/src/main.rs
index 2a78ddaa1..23c75957c 100644
--- a/radicle-cli/src/main.rs
+++ b/radicle-cli/src/main.rs
@@ -3,6 +3,8 @@ use std::io::{self, Write};
use std::{io::ErrorKind, iter, process};
use anyhow::anyhow;
+use clap::builder::styling::Style;
+use clap::builder::Styles;
use clap::{CommandFactory, Parser, Subcommand};
use radicle::version;
@@ -14,6 +16,7 @@ use radicle_cli::terminal as term;
pub const NAME: &str = "rad";
pub const PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
pub const DESCRIPTION: &str = "Radicle command line interface";
+pub const LONG_DESCRIPTION: &str = "Radicle is a distributed GIT forge.";
pub const GIT_HEAD: &str = env!("GIT_HEAD");
pub const TIMESTAMP: &str = env!("GIT_COMMIT_TIME");
pub const VERSION: Version = Version {
@@ -22,10 +25,28 @@ pub const VERSION: Version = Version {
commit: GIT_HEAD,
timestamp: TIMESTAMP,
};
+pub const LONG_VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), " (", env!("GIT_HEAD"), ")");
+pub const HELP_TEMPLATE: &str = r#"
+{before-help}{bin} {version}
+{about-with-newline}
+Usage: {usage}
+{all-args}
+{after-help}
+"#;
+
+/// Radicle command line interface
+///
+/// Radicle is a distributed GIT forge.
#[derive(Parser, Debug)]
-#[command(name = "rad")]
-#[command(about = "The rad CLI", long_about = None)]
+#[command(name = NAME)]
+#[command(version = VERSION)]
+#[command(long_version = LONG_VERSION)]
+#[command(help_template = HELP_TEMPLATE)]
+#[command(propagate_version = true)]
+#[command(propagate_help_template = true)]
+// #[command(styles = Styles::styled().usage(AnsiColor::Blue.on_default()))]
+#[command(styles = Styles::plain().literal(Style::new().bold()))]
struct CliArgs {
#[command(subcommand)]
pub command: Option<Commands>,
@@ -33,6 +54,9 @@ struct CliArgs {
#[derive(Subcommand, Debug)]
enum Commands {
+ /// Manage issues
+ ///
+ /// With issues you can organize your project and use it to discuss bugs and improvements.
Issue(rad_issue::IssueArgs),
}
commit ef2949832b1a684526bce50cfdfea5db260a7d28
Author: Christopher Fredén <me@icetan.org>
Date: Fri Mar 1 02:13:55 2024 +0100
Refactored the issue command using clap
diff --git a/Cargo.lock b/Cargo.lock
index 794b29c47..f2ccb8750 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -528,7 +528,7 @@ dependencies = [
[[package]]
name = "clap"
-version = "4.5.0"
+version = "4.5.1"
dependencies = [
"clap_builder",
"clap_derive",
@@ -536,7 +536,7 @@ dependencies = [
[[package]]
name = "clap_builder"
-version = "4.5.0"
+version = "4.5.1"
dependencies = [
"anstream",
"anstyle",
@@ -546,7 +546,7 @@ dependencies = [
[[package]]
name = "clap_complete"
-version = "4.5.0"
+version = "4.5.1"
dependencies = [
"clap",
"clap_lex",
diff --git a/radicle-cli/src/cli.rs b/radicle-cli/src/cli.rs
index 48660e946..153de9ae5 100644
--- a/radicle-cli/src/cli.rs
+++ b/radicle-cli/src/cli.rs
@@ -1,69 +1,85 @@
use std::ffi::OsString;
-use anyhow::anyhow;
-use clap::{CommandFactory, FromArgMatches, Parser, Subcommand, ValueHint};
+use clap::{Command, FromArgMatches, Subcommand};
use clap_complete::dynamic::shells::CompleteCommand;
+use radicle::identity::Did;
use radicle::issue::Issues;
-use radicle::storage::WriteStorage;
+use radicle::storage::{ReadRepository, ReadStorage};
-use crate::terminal as term;
-use crate::commands::rad_issue;
-use crate::git::Rev;
-
-#[derive(Parser, Debug)]
-#[command(name = "rad")]
-#[command(about = "The rad CLI", long_about = None)]
-struct CliArgs {
- #[command(subcommand)]
- command: Option<Commands>,
-}
-
-#[derive(Subcommand, Debug)]
-enum Commands {
- Issue(IssueArgs),
-}
-
-#[derive(Parser, Debug)]
-struct IssueArgs {
- #[command(subcommand)]
- command: Option<IssueCommands>,
+pub fn get_assignee_did_hints(input: &str) -> Option<Vec<String>> {
+ let (_, rid) = radicle::rad::cwd().ok()?;
+ radicle::Profile::load()
+ .ok()
+ .and_then(|profile| profile.storage.repository(rid).ok())
+ .and_then(|repo| {
+ Issues::open(&repo).ok().and_then(|issues| {
+ issues
+ .all()
+ .map(|issues| {
+ issues
+ .flat_map(|issue| {
+ issue.map_or(vec![], |(_, issue)| {
+ issue.assignees().cloned().collect::<Vec<_>>()
+ })
+ })
+ .filter_map(|did| {
+ let did = did.to_human();
+ did.starts_with(input).then(|| String::from(did))
+ })
+ .collect::<Vec<_>>()
+ })
+ .ok()
+ })
+ })
}
-#[derive(Subcommand, Debug)]
-enum IssueCommands {
- List,
- Show {
- #[clap(value_hint = ISSUE_ID_HINT)]
- id: String,
- },
+pub fn get_issue_id_hints(input: &str) -> Option<Vec<String>> {
+ let (_, rid) = radicle::rad::cwd().ok()?;
+ radicle::Profile::load()
+ .ok()
+ .and_then(|profile| profile.storage.repository(rid).ok())
+ .and_then(|repo| {
+ Issues::open(&repo).ok().and_then(|issues| {
+ issues
+ .all()
+ .map(|issues| {
+ issues
+ .filter_map(|issue| {
+ if let Ok((id, _)) = issue {
+ let id = id.to_string();
+ if id.starts_with(input) {
+ return Some(String::from(id.split_at(8).0));
+ }
+ }
+ None
+ })
+ .collect::<Vec<_>>()
+ })
+ .ok()
+ })
+ })
}
-const ISSUE_ID_HINT: ValueHint = ValueHint::Dynamic(get_issue_ids);
-
-fn get_issue_ids(input: &str) -> Option<Vec<String>> {
- let profile = term::profile().ok()?;
-
+pub fn get_did_hints<R: ReadRepository + radicle::cob::Store>(input: &str) -> Option<Vec<String>> {
let (_, rid) = radicle::rad::cwd().ok()?;
- let repo = profile.storage.repository_mut(rid).ok()?;
- let issues = Issues::open(&repo).ok()?;
-
- let completions = issues.all().ok()?
- .filter_map(|issue| {
- if let Ok((id, _)) = issue {
- let id = id.to_string();
- if id.starts_with(input) {
- return Some(String::from(id.split_at(8).0));
- }
- }
- None
+ radicle::Profile::load()
+ .ok()
+ .and_then(|profile| profile.storage.repository(rid).ok())
+ .and_then(|repo| {
+ repo.remote_ids()
+ .map(|issues| {
+ issues
+ .filter_map(|id| {
+ let id = id.map(|id| Did::from(id).to_human()).ok()?;
+ id.starts_with(input).then_some(id)
+ })
+ .collect::<Vec<_>>()
+ })
+ .ok()
})
- .collect::<Vec<_>>();
-
- Some(completions)
}
-pub fn completer(args: Vec<OsString>) -> () {
- let cmd = CliArgs::command();
+pub fn completer(cmd: Command, args: Vec<OsString>) -> () {
let mut cmd = CompleteCommand::augment_subcommands(cmd);
let matches = cmd.clone().get_matches();
@@ -74,36 +90,84 @@ pub fn completer(args: Vec<OsString>) -> () {
}
}
-pub fn to_issue_options() -> anyhow::Result<rad_issue::Options> {
- let args = CliArgs::parse();
-
- let options = match args {
- CliArgs { command } => match command {
- Some(Commands::Issue(IssueArgs {
- command: subcommand,
- })) => match subcommand {
- Some(IssueCommands::List) => Some(rad_issue::Options {
- op: rad_issue::Operation::List {
- assigned: None,
- state: None,
- },
- announce: true,
- quiet: false,
- }),
- Some(IssueCommands::Show { id }) => Some(rad_issue::Options {
- op: rad_issue::Operation::Show {
- id: Rev::from(id),
- format: term::issue::Format::Full,
- debug: false,
- },
- announce: true,
- quiet: false,
- }),
- _ => None,
- },
- _ => None,
- },
- };
-
- options.ok_or(anyhow!("Command not implemented FIXME!"))
-}
+// pub fn to_issue_options() -> anyhow::Result<rad_issue::Options> {
+// let args = CliArgs::parse();
+
+// // Default to List command.
+// // let command = args.command.unwrap_or(Commands::Issue(IssueArgs {
+// // command: Some(IssueCommands::List(ListArgs {
+// // assigned: None,
+// // filter: ListFilter::All,
+// // })),
+// // header: false,
+// // quiet: false,
+// // no_announce: false,
+// // }));
+
+// let options = match args {
+// CliArgs { command } => match command {
+// Some(Commands::Issue(IssueArgs {
+// command: subcommand,
+// quiet,
+// no_announce,
+// header,
+// })) => match subcommand {
+// Some(IssueCommands::List(args)) => Some(rad_issue::Options {
+// op: rad_issue::Operation::List {
+// assigned: args
+// .assigned
+// .map(|did| {
+// let did =
+// term::args::did(&OsString::from(format!("did:key:{did}")))?;
+// Ok::<Option<Assigned>, anyhow::Error>(Some(Assigned::Peer(did)))
+// })
+// .unwrap_or(Ok(None))?,
+// state: match args.filter {
+// ListFilter::All => None,
+// ListFilter::Open => Some(radicle::cob::issue::State::Open),
+// ListFilter::Closed => Some(State::Closed {
+// reason: CloseReason::Other,
+// }),
+// ListFilter::Solved => Some(State::Closed {
+// reason: CloseReason::Solved,
+// }),
+// },
+// },
+// announce: !no_announce,
+// quiet: quiet,
+// }),
+// Some(IssueCommands::Show(args)) => Some(rad_issue::Options {
+// op: rad_issue::Operation::Show {
+// id: Rev::from(args.id),
+// format: if header {
+// term::issue::Format::Header
+// } else {
+// term::issue::Format::Full
+// },
+// debug: args.debug,
+// },
+// announce: !no_announce,
+// quiet: quiet,
+// }),
+// // Default `issue` subcommand is `list`.
+// _ => Some(rad_issue::Options {
+// op: rad_issue::Operation::List {
+// assigned: None,
+// state: None,
+// },
+// announce: false,
+// quiet: false,
+// }),
+// },
+// _ => None,
+// },
+// };
+
+// let Some(option) = options else {
+// CliArgs::command().write_help(&mut std::io::stdout())?;
+// println!();
+// process::exit(0);
+// };
+
+// Ok(option)
+// }
diff --git a/radicle-cli/src/commands/issue.rs b/radicle-cli/src/commands/issue.rs
index 84d14450c..e716ce51d 100644
--- a/radicle-cli/src/commands/issue.rs
+++ b/radicle-cli/src/commands/issue.rs
@@ -1,11 +1,8 @@
#[path = "issue/cache.rs"]
mod cache;
-use std::collections::BTreeSet;
-use std::ffi::OsString;
-use std::str::FromStr;
-
-use anyhow::{anyhow, Context as _};
+use anyhow::Context as _;
+use clap::{Parser, Subcommand, ValueHint};
use radicle::cob::common::{Label, Reaction};
use radicle::cob::issue;
@@ -20,10 +17,11 @@ use radicle::storage::{ReadRepository, WriteRepository, WriteStorage};
use radicle::Profile;
use radicle::{cob, Node};
+use crate::cli::{get_assignee_did_hints, get_issue_id_hints};
use crate::git::Rev;
use crate::node;
use crate::terminal as term;
-use crate::terminal::args::{Args, Error, Help};
+use crate::terminal::args::Help;
use crate::terminal::format::Author;
use crate::terminal::issue::Format;
use crate::terminal::patch::Message;
@@ -77,22 +75,6 @@ Options
"#,
};
-#[derive(Default, Debug, PartialEq, Eq)]
-pub enum OperationName {
- Assign,
- Edit,
- Open,
- Comment,
- Delete,
- Label,
- #[default]
- List,
- React,
- Show,
- State,
- Cache,
-}
-
/// Command line Peer argument.
#[derive(Default, Debug, PartialEq, Eq)]
pub enum Assigned {
@@ -101,500 +83,368 @@ pub enum Assigned {
Peer(Did),
}
-#[derive(Debug, PartialEq, Eq)]
-pub enum Operation {
+#[derive(Parser, Debug)]
+pub struct IssueArgs {
+ #[command(subcommand)]
+ command: Option<IssueCommands>,
+
+ /// Don't print anything
+ #[arg(short, long)]
+ quiet: bool,
+
+ /// Don't announce issue to peers
+ #[arg(long)]
+ #[arg(value_name = "no-announce")]
+ no_announce: bool,
+
+ /// Show only the issue header, hiding the comments
+ #[arg(long)]
+ header: bool,
+
+ #[arg(long, short)]
+ repo: Option<RepoId>,
+}
+
+#[derive(Subcommand, Debug)]
+enum IssueCommands {
+ /// Delete an issue.
+ Delete {
+ #[arg(value_name = "issue-id")]
+ #[clap(value_hint = ValueHint::Dynamic(get_issue_id_hints))]
+ id: Rev,
+ },
+
+ /// Edit an issue.
Edit {
+ #[arg(value_name = "issue-id")]
+ #[clap(value_hint = ValueHint::Dynamic(get_issue_id_hints))]
id: Rev,
+
+ #[arg(long, short)]
title: Option<String>,
+
+ #[arg(long, short)]
description: Option<String>,
},
+
+ List {
+ #[clap(value_hint = ValueHint::Dynamic(get_assignee_did_hints))]
+ #[arg(long, name = "did")]
+ assigned: Option<Did>,
+
+ #[clap(value_enum, default_value_t=StateFilter::All)]
+ state: StateFilter,
+ },
+
Open {
+ #[arg(long, short)]
title: Option<String>,
+
+ #[arg(long, short)]
description: Option<String>,
+
+ #[arg(long)]
labels: Vec<Label>,
+
+ #[arg(long)]
assignees: Vec<Did>,
},
- Show {
- id: Rev,
- format: Format,
- debug: bool,
- },
- Comment {
- id: Rev,
- message: Message,
- reply_to: Option<Rev>,
- },
- State {
- id: Rev,
- state: State,
- },
- Delete {
- id: Rev,
- },
+
React {
+ #[arg(value_name = "issue-id")]
+ #[clap(value_hint = ValueHint::Dynamic(get_issue_id_hints))]
id: Rev,
+
+ #[arg(long = "emoji")]
+ #[arg(value_name = "char")]
reaction: Reaction,
+
+ #[arg(long = "to")]
+ #[arg(value_name = "comment")]
+ // TODO: Add dynamic hint for comment ids
comment_id: Option<thread::CommentId>,
},
+
+ /// Manage assignees of an issue
Assign {
+ #[clap(value_hint = ValueHint::Dynamic(get_issue_id_hints))]
+ #[arg(value_name = "issue-id")]
id: Rev,
- opts: AssignOptions,
+
+ /// Add an assignee to the issue (may be specified multiple times).
+ #[arg(long, short)]
+ #[arg(value_name = "did")]
+ #[arg(action = clap::ArgAction::Append)]
+ add: Vec<Did>,
+
+ /// Delete an assignee from the issue (may be specified multiple times).
+ #[arg(long, short)]
+ #[arg(value_name = "did")]
+ #[arg(action = clap::ArgAction::Append)]
+ delete: Vec<Did>,
},
+
+ /// Update lables on an issue.
Label {
+ /// The issue to label.
+ #[arg(value_name = "issue-id")]
+ #[clap(value_hint = ValueHint::Dynamic(get_issue_id_hints))]
id: Rev,
- opts: LabelOptions,
+
+ /// Add an assignee to the issue (may be specified multiple times).
+ #[arg(long, short)]
+ #[arg(value_name = "label")]
+ #[arg(action = clap::ArgAction::Append)]
+ add: Vec<Label>,
+
+ /// Delete an assignee from the issue (may be specified multiple times).
+ #[arg(long, short)]
+ #[arg(value_name = "label")]
+ #[arg(action = clap::ArgAction::Append)]
+ delete: Vec<Label>,
},
- List {
- assigned: Option<Assigned>,
- state: Option<State>,
+
+ /// Add a comment to an issue.
+ Comment {
+ #[arg(value_name = "issue-id")]
+ #[clap(value_hint = ValueHint::Dynamic(get_issue_id_hints))]
+ id: Rev,
+
+ /// Message text.
+ #[arg(long, short)]
+ #[arg(value_name = "message")]
+ message: Message,
+
+ #[arg(long, name = "comment-id")]
+ reply_to: Option<Rev>,
+ },
+
+ Show {
+ #[arg(value_name = "issue-id")]
+ #[clap(value_hint = ValueHint::Dynamic(get_issue_id_hints))]
+ id: Rev,
+
+ /// Show the issue as Rust debug output
+ #[arg(long)]
+ debug: bool,
+ },
+
+ State {
+ #[arg(value_name = "issue-id")]
+ #[clap(value_hint = ValueHint::Dynamic(get_issue_id_hints))]
+ id: Rev,
+
+ #[clap(value_enum)]
+ state: StateChoice,
},
Cache {
+ #[arg(value_name = "issue-id")]
id: Option<Rev>,
},
}
-#[derive(Debug, Default, PartialEq, Eq)]
-pub struct AssignOptions {
- pub add: BTreeSet<Did>,
- pub delete: BTreeSet<Did>,
+#[derive(clap::ValueEnum, Clone, Debug)]
+enum StateFilter {
+ All,
+ Closed,
+ Open,
+ Solved,
}
-#[derive(Debug, Default, PartialEq, Eq)]
-pub struct LabelOptions {
- pub add: BTreeSet<Label>,
- pub delete: BTreeSet<Label>,
+fn to_state_filter(list_state: StateFilter) -> Option<State> {
+ match list_state {
+ StateFilter::All => None,
+ StateFilter::Open => Some(radicle::cob::issue::State::Open),
+ StateFilter::Closed => Some(State::Closed {
+ reason: CloseReason::Other,
+ }),
+ StateFilter::Solved => Some(State::Closed {
+ reason: CloseReason::Solved,
+ }),
+ }
}
-#[derive(Debug)]
-pub struct Options {
- pub op: Operation,
- pub repo: Option<RepoId>,
- pub announce: bool,
- pub quiet: bool,
+#[derive(clap::ValueEnum, Clone, Debug)]
+enum StateChoice {
+ Closed,
+ Open,
+ Solved,
}
-impl Args for Options {
- fn from_args(args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> {
- use lexopt::prelude::*;
-
- let mut parser = lexopt::Parser::from_args(args);
- let mut op: Option<OperationName> = None;
- let mut id: Option<Rev> = None;
- let mut assigned: Option<Assigned> = None;
- let mut title: Option<String> = None;
- let mut reaction: Option<Reaction> = None;
- let mut comment_id: Option<thread::CommentId> = None;
- let mut description: Option<String> = None;
- let mut state: Option<State> = Some(State::Open);
- let mut labels = Vec::new();
- let mut assignees = Vec::new();
- let mut format = Format::default();
- let mut message = Message::default();
- let mut reply_to = None;
- let mut announce = true;
- let mut quiet = false;
- let mut debug = false;
- let mut assign_opts = AssignOptions::default();
- let mut label_opts = LabelOptions::default();
- let mut repo = None;
-
- while let Some(arg) = parser.next()? {
- match arg {
- Long("help") | Short('h') => {
- return Err(Error::Help.into());
- }
-
- // List options.
- Long("all") if op.is_none() || op == Some(OperationName::List) => {
- state = None;
- }
- Long("closed") if op.is_none() || op == Some(OperationName::List) => {
- state = Some(State::Closed {
- reason: CloseReason::Other,
- });
- }
- Long("open") if op.is_none() || op == Some(OperationName::List) => {
- state = Some(State::Open);
- }
- Long("solved") if op.is_none() || op == Some(OperationName::List) => {
- state = Some(State::Closed {
- reason: CloseReason::Solved,
- });
- }
-
- // Open 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)) => {
- let val = parser.value()?;
- let name = term::args::string(&val);
- let label = Label::new(name)?;
-
- labels.push(label);
- }
- Long("assign") if op == Some(OperationName::Open) => {
- let val = parser.value()?;
- let did = term::args::did(&val)?;
-
- assignees.push(did);
- }
- Long("description") if op == Some(OperationName::Open) => {
- description = Some(parser.value()?.to_string_lossy().into());
- }
-
- // State options.
- Long("closed") if op == Some(OperationName::State) => {
- state = Some(State::Closed {
- reason: CloseReason::Other,
- });
- }
- Long("open") if op == Some(OperationName::State) => {
- state = Some(State::Open);
- }
- Long("solved") if op == Some(OperationName::State) => {
- state = Some(State::Closed {
- reason: CloseReason::Solved,
- });
- }
-
- // React options.
- Long("emoji") if op == Some(OperationName::React) => {
- if let Some(emoji) = parser.value()?.to_str() {
- reaction =
- Some(Reaction::from_str(emoji).map_err(|_| anyhow!("invalid emoji"))?);
- }
- }
- Long("to") if op == Some(OperationName::React) => {
- let oid: String = parser.value()?.to_string_lossy().into();
- comment_id = Some(oid.parse()?);
- }
-
- // Show options.
- Long("format") if op == Some(OperationName::Show) => {
- let val = parser.value()?;
- let val = term::args::string(&val);
-
- match val.as_str() {
- "header" => format = Format::Header,
- "full" => format = Format::Full,
- _ => anyhow::bail!("unknown format '{val}'"),
- }
- }
- Long("debug") if op == Some(OperationName::Show) => {
- debug = true;
- }
-
- // Comment options.
- Long("message") | Short('m') if op == Some(OperationName::Comment) => {
- let val = parser.value()?;
- let txt = term::args::string(&val);
-
- message.append(&txt);
- }
- Long("reply-to") if op == Some(OperationName::Comment) => {
- let val = parser.value()?;
- let rev = term::args::rev(&val)?;
-
- reply_to = Some(rev);
- }
-
- // Assign options
- Short('a') | Long("add") if op == Some(OperationName::Assign) => {
- assign_opts.add.insert(term::args::did(&parser.value()?)?);
- }
- Short('d') | Long("delete") if op == Some(OperationName::Assign) => {
- assign_opts
- .delete
- .insert(term::args::did(&parser.value()?)?);
- }
- Long("assigned") | Short('a') if assigned.is_none() => {
- if let Ok(val) = parser.value() {
- let peer = term::args::did(&val)?;
- assigned = Some(Assigned::Peer(peer));
- } else {
- assigned = Some(Assigned::Me);
- }
- }
-
- // 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('d') | Long("delete") if matches!(op, Some(OperationName::Label)) => {
- let val = parser.value()?;
- let name = term::args::string(&val);
- let label = Label::new(name)?;
-
- label_opts.delete.insert(label);
- }
-
- // Options.
- Long("no-announce") => {
- announce = false;
- }
- Long("quiet") | Short('q') => {
- quiet = true;
- }
- Long("repo") => {
- let val = parser.value()?;
- let rid = term::args::rid(&val)?;
-
- repo = Some(rid);
- }
-
- Value(val) if op.is_none() => match val.to_string_lossy().as_ref() {
- "c" | "comment" => op = Some(OperationName::Comment),
- "w" | "show" => op = Some(OperationName::Show),
- "d" | "delete" => op = Some(OperationName::Delete),
- "e" | "edit" => op = Some(OperationName::Edit),
- "l" | "list" => op = Some(OperationName::List),
- "o" | "open" => op = Some(OperationName::Open),
- "r" | "react" => op = Some(OperationName::React),
- "s" | "state" => op = Some(OperationName::State),
- "assign" => op = Some(OperationName::Assign),
- "label" => op = Some(OperationName::Label),
- "cache" => op = Some(OperationName::Cache),
-
- unknown => anyhow::bail!("unknown operation '{}'", unknown),
- },
- Value(val) if op.is_some() => {
- let val = term::args::rev(&val)?;
- id = Some(val);
- }
- _ => {
- return Err(anyhow!(arg.unexpected()));
- }
- }
- }
-
- let op = match op.unwrap_or_default() {
- OperationName::Edit => Operation::Edit {
- id: id.ok_or_else(|| anyhow!("an issue must be provided"))?,
- title,
- description,
- },
- OperationName::Open => Operation::Open {
- title,
- description,
- labels,
- assignees,
- },
- OperationName::Comment => Operation::Comment {
- id: id.ok_or_else(|| anyhow!("an issue must be provided"))?,
- message,
- reply_to,
- },
- OperationName::Show => Operation::Show {
- id: id.ok_or_else(|| anyhow!("an issue must be provided"))?,
- format,
- debug,
- },
- OperationName::State => Operation::State {
- id: id.ok_or_else(|| anyhow!("an issue must be provided"))?,
- state: state.ok_or_else(|| anyhow!("a state operation must be provided"))?,
- },
- OperationName::React => Operation::React {
- id: id.ok_or_else(|| anyhow!("an issue must be provided"))?,
- reaction: reaction.ok_or_else(|| anyhow!("a reaction emoji must be provided"))?,
- comment_id,
- },
- OperationName::Delete => Operation::Delete {
- id: id.ok_or_else(|| anyhow!("an issue to remove must be provided"))?,
- },
- OperationName::Assign => Operation::Assign {
- id: id.ok_or_else(|| anyhow!("an issue to label must be provided"))?,
- opts: assign_opts,
- },
- OperationName::Label => Operation::Label {
- id: id.ok_or_else(|| anyhow!("an issue to label must be provided"))?,
- opts: label_opts,
- },
- OperationName::List => Operation::List { assigned, state },
- OperationName::Cache => Operation::Cache { id },
- };
-
- Ok((
- Options {
- op,
- repo,
- announce,
- quiet,
- },
- vec![],
- ))
+fn to_state_choice(choice: StateChoice) -> State {
+ match choice {
+ StateChoice::Open => radicle::cob::issue::State::Open,
+ StateChoice::Closed => State::Closed {
+ reason: CloseReason::Other,
+ },
+ StateChoice::Solved => State::Closed {
+ reason: CloseReason::Solved,
+ },
}
}
-pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
+pub fn run(args: IssueArgs, ctx: impl term::Context) -> anyhow::Result<()> {
let profile = ctx.profile()?;
- let rid = if let Some(rid) = options.repo {
+ let rid = if let Some(rid) = args.repo {
rid
} else {
radicle::rad::cwd().map(|(_, rid)| rid)?
};
let repo = profile.storage.repository_mut(rid)?;
- let announce = options.announce
- && matches!(
- &options.op,
- Operation::Open { .. }
- | Operation::React { .. }
- | Operation::State { .. }
- | Operation::Delete { .. }
- | Operation::Assign { .. }
- | Operation::Label { .. }
- );
let mut issues = profile.issues_mut(&repo)?;
- match options.op {
- Operation::Edit {
- id,
- title,
- description,
- } => {
- let signer = term::signer(&profile)?;
- let issue = edit(&mut issues, &repo, id, title, description, &signer)?;
- if !options.quiet {
- term::issue::show(&issue, issue.id(), Format::Header, &profile)?;
+ if let Some(command) = args.command {
+ let announce = !args.no_announce
+ && matches!(
+ &command,
+ IssueCommands::Open { .. }
+ | IssueCommands::React { .. }
+ | IssueCommands::State { .. }
+ | IssueCommands::Delete { .. }
+ | IssueCommands::Assign { .. }
+ | IssueCommands::Label { .. }
+ );
+
+ match command {
+ IssueCommands::Delete { id } => {
+ let signer = term::signer(&profile)?;
+ let id = id.resolve(&repo.backend)?;
+ issues.remove(&id, &signer)?;
}
- }
- Operation::Open {
- title: Some(title),
- description: Some(description),
- labels,
- assignees,
- } => {
- let signer = term::signer(&profile)?;
- let issue = issues.create(title, description, &labels, &assignees, [], &signer)?;
- if !options.quiet {
- term::issue::show(&issue, issue.id(), Format::Header, &profile)?;
+ IssueCommands::Edit {
+ id,
+ title,
+ description,
+ } => {
+ let signer = term::signer(&profile)?;
+ let issue = edit(&mut issues, &repo, id, title, description, &signer)?;
+ if !args.quiet {
+ term::issue::show(&issue, issue.id(), Format::Header, &profile)?;
+ }
}
- }
- Operation::Comment {
- id,
- message,
- reply_to,
- } => {
- let signer = term::signer(&profile)?;
- let issue_id = id.resolve::<cob::ObjectId>(&repo.backend)?;
- let mut issue = issues.get_mut(&issue_id)?;
- let (body, reply_to) = prompt_comment(message, reply_to, &issue, &repo)?;
- let comment_id = issue.comment(body, reply_to, vec![], &signer)?;
-
- if options.quiet {
- term::print(comment_id);
- } else {
- let comment = issue.thread().comment(&comment_id).unwrap();
- term::comment::widget(&comment_id, comment, &profile).print();
+ IssueCommands::List { assigned, state } => {
+ let assigned = assigned.map(Assigned::Peer);
+ let state = to_state_filter(state);
+ list(issues, &assigned, &state, &profile)?;
}
- }
- Operation::Show { id, format, debug } => {
- let id = id.resolve(&repo.backend)?;
- let issue = issues
- .get(&id)?
- .context("No issue with the given ID exists")?;
- if debug {
- println!("{:#?}", issue);
- } else {
- term::issue::show(&issue, &id, format, &profile)?;
+ IssueCommands::Show { id, debug } => {
+ let format = if args.header {
+ term::issue::Format::Header
+ } else {
+ term::issue::Format::Full
+ };
+
+ let id = id.resolve(&repo.backend)?;
+
+ let issue = issues
+ .get(&id)?
+ .context("No issue with the given ID exists")?;
+ if debug {
+ println!("{:#?}", issue);
+ } else {
+ term::issue::show(&issue, &id, format, &profile)?;
+ }
}
- }
- Operation::State { id, state } => {
- let signer = term::signer(&profile)?;
- let id = id.resolve(&repo.backend)?;
- let mut issue = issues.get_mut(&id)?;
- issue.lifecycle(state, &signer)?;
- }
- Operation::React {
- id,
- reaction,
- comment_id,
- } => {
- let id = id.resolve(&repo.backend)?;
- if let Ok(mut issue) = issues.get_mut(&id) {
+ IssueCommands::State { id, state } => {
let signer = term::signer(&profile)?;
- let comment_id = comment_id.unwrap_or_else(|| {
- let (comment_id, _) = term::io::comment_select(&issue).unwrap();
- *comment_id
- });
- issue.react(comment_id, reaction, true, &signer)?;
+ let id = id.resolve(&repo.backend)?;
+ let mut issue = issues.get_mut(&id)?;
+ issue.lifecycle(to_state_choice(state), &signer)?;
+ }
+ IssueCommands::Assign { id, add, delete } => {
+ let signer = term::signer(&profile)?;
+ let id = id.resolve(&repo.backend)?;
+ let Ok(mut issue) = issues.get_mut(&id) else {
+ anyhow::bail!("Issue `{id}` not found");
+ };
+ let assignees = issue
+ .assignees()
+ .filter(|did| !delete.contains(did))
+ .chain(add.iter())
+ .cloned()
+ .collect::<Vec<_>>();
+ issue.assign(assignees, &signer)?;
+ }
+ IssueCommands::Comment {
+ id,
+ message,
+ reply_to,
+ } => {
+ let signer = term::signer(&profile)?;
+ let issue_id = id.resolve::<cob::ObjectId>(&repo.backend)?;
+ let mut issue = issues.get_mut(&issue_id)?;
+ let (body, reply_to) = prompt_comment(message, reply_to, &issue, &repo)?;
+ let comment_id = issue.comment(body, reply_to, vec![], &signer)?;
+
+ if args.quiet {
+ term::print(comment_id);
+ } else {
+ let comment = issue.thread().comment(&comment_id).unwrap();
+ term::comment::widget(&comment_id, comment, &profile).print();
+ }
+ }
+ IssueCommands::React {
+ id,
+ comment_id,
+ reaction,
+ } => {
+ let signer = term::signer(&profile)?;
+ let id = id.resolve(&repo.backend)?;
+ if let Ok(mut issue) = issues.get_mut(&id) {
+ let comment_id = comment_id.unwrap_or_else(|| {
+ let (comment_id, _) = term::io::comment_select(&issue).unwrap();
+ *comment_id
+ });
+ issue.react(comment_id, reaction, true, &signer)?;
+ }
+ }
+ IssueCommands::Label { id, add, delete } => {
+ let signer = term::signer(&profile)?;
+ 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(|did| !delete.contains(did))
+ .chain(add.iter())
+ .cloned()
+ .collect::<Vec<_>>();
+ issue.label(labels, &signer)?;
+ }
+ IssueCommands::Open {
+ ref title,
+ ref description,
+ ref labels,
+ ref assignees,
+ } => {
+ let signer = term::signer(&profile)?;
+ open(
+ title.clone(),
+ description.clone(),
+ labels.to_vec(),
+ assignees.to_vec(),
+ args.quiet,
+ &mut issues,
+ &signer,
+ &profile,
+ )?;
+ }
+ IssueCommands::Cache { id } => {
+ let id = id.map(|id| id.resolve(&repo.backend)).transpose()?;
+ cache::run(id, &repo, &profile)?;
}
}
- Operation::Open {
- ref title,
- ref description,
- ref labels,
- ref assignees,
- } => {
- let signer = term::signer(&profile)?;
- open(
- title.clone(),
- description.clone(),
- labels.to_vec(),
- assignees.to_vec(),
- &options,
- &mut issues,
- &signer,
- &profile,
- )?;
- }
- Operation::Assign {
- id,
- opts: AssignOptions { add, delete },
- } => {
- let signer = term::signer(&profile)?;
- let id = id.resolve(&repo.backend)?;
- let Ok(mut issue) = issues.get_mut(&id) else {
- anyhow::bail!("Issue `{id}` not found");
- };
- let assignees = issue
- .assignees()
- .filter(|did| !delete.contains(did))
- .chain(add.iter())
- .cloned()
- .collect::<Vec<_>>();
- issue.assign(assignees, &signer)?;
- }
- Operation::Label {
- id,
- opts: LabelOptions { add, delete },
- } => {
- let signer = term::signer(&profile)?;
- 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(|did| !delete.contains(did))
- .chain(add.iter())
- .cloned()
- .collect::<Vec<_>>();
- issue.label(labels, &signer)?;
- }
- Operation::List { assigned, state } => {
- list(issues, &assigned, &state, &profile)?;
- }
- Operation::Delete { id } => {
- let signer = term::signer(&profile)?;
- let id = id.resolve(&repo.backend)?;
- issues.remove(&id, &signer)?;
- }
- Operation::Cache { id } => {
- let id = id.map(|id| id.resolve(&repo.backend)).transpose()?;
- cache::run(id, &repo, &profile)?;
- }
- }
- if announce {
- let mut node = Node::new(profile.socket());
- node::announce(rid, &mut node)?;
- }
+ if announce {
+ let mut node = Node::new(profile.socket());
+ node::announce(rid, &mut node)?;
+ }
+ } else {
+ // Default `issue` subcommand is `list`.
+ list(issues, &None, &None, &profile)?;
+ };
Ok(())
}
@@ -710,7 +560,7 @@ fn open<R, G>(
description: Option<String>,
labels: Vec<Label>,
assignees: Vec<Did>,
- options: &Options,
+ quiet: bool,
cache: &mut issue::Cache<issue::Issues<'_, R>, cob::cache::StoreWriter>,
signer: &G,
profile: &Profile,
@@ -735,7 +585,7 @@ where
signer,
)?;
- if !options.quiet {
+ if !quiet {
term::issue::show(&issue, issue.id(), Format::Header, profile)?;
}
Ok(())
diff --git a/radicle-cli/src/main.rs b/radicle-cli/src/main.rs
index 04cb1bae0..2a78ddaa1 100644
--- a/radicle-cli/src/main.rs
+++ b/radicle-cli/src/main.rs
@@ -3,11 +3,13 @@ use std::io::{self, Write};
use std::{io::ErrorKind, iter, process};
use anyhow::anyhow;
+use clap::{CommandFactory, Parser, Subcommand};
-use radicle::version::Version;
+use radicle::version;
+use radicle_cli::cli;
+use radicle_cli::commands::rad_issue;
use radicle_cli::commands::*;
use radicle_cli::terminal as term;
-use radicle_cli::cli;
pub const NAME: &str = "rad";
pub const PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
@@ -21,6 +23,19 @@ pub const VERSION: Version = Version {
timestamp: TIMESTAMP,
};
+#[derive(Parser, Debug)]
+#[command(name = "rad")]
+#[command(about = "The rad CLI", long_about = None)]
+struct CliArgs {
+ #[command(subcommand)]
+ pub command: Option<Commands>,
+}
+
+#[derive(Subcommand, Debug)]
+enum Commands {
+ Issue(rad_issue::IssueArgs),
+}
+
#[derive(Debug)]
enum Command {
Other(Vec<OsString>),
@@ -226,11 +241,21 @@ fn run_other(exe: &str, args: &[OsString]) -> Result<(), Option<anyhow::Error>>
);
}
"issue" => {
- let options = cli::to_issue_options()?;
- rad_issue::run(options, term::profile()?)?;
+ let args_ = CliArgs::parse();
+ if let Some(command) = args_.command {
+ match command {
+ Commands::Issue(args_) => rad_issue::run(
+ args_,
+ radicle::Profile::load()
+ .map_err(|e| anyhow!(e))?,
+ )?,
+ }
+ // If clap parsed a command short circuit.
+ // return Ok(());
+ }
}
"complete" => {
- cli::completer(args.to_vec());
+ cli::completer(CliArgs::command(), args.to_vec());
}
"ls" => {
term::run_command_args::<rad_ls::Options, _>(rad_ls::HELP, rad_ls::run, args.to_vec());
diff --git a/radicle-cli/src/terminal/patch.rs b/radicle-cli/src/terminal/patch.rs
index cb2efa24b..0009eaad5 100644
--- a/radicle-cli/src/terminal/patch.rs
+++ b/radicle-cli/src/terminal/patch.rs
@@ -108,6 +108,14 @@ impl Message {
}
}
+impl From<String> for Message {
+ fn from(value: String) -> Self {
+ let mut message = Message::default();
+ message.append(&value);
+ message
+ }
+}
+
pub const PATCH_MSG: &str = r#"
<!--
Please enter a patch message for your changes. An empty
commit 92cbaa6d97ed7be579fc2e1da7ddff71595f117b
Author: Christopher Fredén <me@icetan.org>
Date: Fri Feb 23 09:43:32 2024 +0100
Add clap for parsing cli args
diff --git a/Cargo.lock b/Cargo.lock
index cc59c204a..794b29c47 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -526,6 +526,50 @@ dependencies = [
"zeroize",
]
+[[package]]
+name = "clap"
+version = "4.5.0"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.0"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_complete"
+version = "4.5.0"
+dependencies = [
+ "clap",
+ "clap_lex",
+ "is_executable",
+ "pathdiff",
+ "shlex",
+ "unicode-xid",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.5.0"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.0"
+
[[package]]
name = "colorchoice"
version = "1.0.0"
@@ -1424,6 +1468,12 @@ dependencies = [
"allocator-api2",
]
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
[[package]]
name = "hermit-abi"
version = "0.3.4"
@@ -1614,6 +1664,15 @@ dependencies = [
"popol",
]
+[[package]]
+name = "is_executable"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa9acdc6d67b75e626ad644734e8bc6df893d9cd2a834129065d3dd6158ea9c8"
+dependencies = [
+ "winapi",
+]
+
[[package]]
name = "itoa"
version = "1.0.10"
@@ -2062,6 +2121,12 @@ dependencies = [
"windows-targets 0.48.5",
]
+[[package]]
+name = "pathdiff"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
+
[[package]]
name = "pbkdf2"
version = "0.12.2"
@@ -2317,6 +2382,8 @@ version = "0.8.0"
dependencies = [
"anyhow",
"chrono",
+ "clap",
+ "clap_complete",
"git-ref-format",
"lexopt",
"localtime",
@@ -3103,6 +3170,12 @@ dependencies = [
"zeroize",
]
+[[package]]
+name = "strsim"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
+
[[package]]
name = "subtle"
version = "2.5.0"
@@ -3610,6 +3683,12 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
+[[package]]
+name = "unicode-xid"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
+
[[package]]
name = "universal-hash"
version = "0.5.1"
diff --git a/radicle-cli/Cargo.toml b/radicle-cli/Cargo.toml
index 1a9e8b3dc..d6b4b5e09 100644
--- a/radicle-cli/Cargo.toml
+++ b/radicle-cli/Cargo.toml
@@ -45,6 +45,9 @@ tree-sitter-bash = { version = "0.20" }
tree-sitter-go = { version = "0.20.0" }
tree-sitter-md = { version = "0.1.5" }
zeroize = { version = "1.1" }
+clap = { path = "/home/icetan/src/radicle/clap", features = ["derive"] }
+clap_complete = { path = "/home/icetan/src/radicle/clap/clap_complete", features = ["unstable-dynamic"] }
+
[dependencies.radicle]
version = "0"
diff --git a/radicle-cli/src/cli.rs b/radicle-cli/src/cli.rs
new file mode 100644
index 000000000..48660e946
--- /dev/null
+++ b/radicle-cli/src/cli.rs
@@ -0,0 +1,109 @@
+use std::ffi::OsString;
+
+use anyhow::anyhow;
+use clap::{CommandFactory, FromArgMatches, Parser, Subcommand, ValueHint};
+use clap_complete::dynamic::shells::CompleteCommand;
+use radicle::issue::Issues;
+use radicle::storage::WriteStorage;
+
+use crate::terminal as term;
+use crate::commands::rad_issue;
+use crate::git::Rev;
+
+#[derive(Parser, Debug)]
+#[command(name = "rad")]
+#[command(about = "The rad CLI", long_about = None)]
+struct CliArgs {
+ #[command(subcommand)]
+ command: Option<Commands>,
+}
+
+#[derive(Subcommand, Debug)]
+enum Commands {
+ Issue(IssueArgs),
+}
+
+#[derive(Parser, Debug)]
+struct IssueArgs {
+ #[command(subcommand)]
+ command: Option<IssueCommands>,
+}
+
+#[derive(Subcommand, Debug)]
+enum IssueCommands {
+ List,
+ Show {
+ #[clap(value_hint = ISSUE_ID_HINT)]
+ id: String,
+ },
+}
+
+const ISSUE_ID_HINT: ValueHint = ValueHint::Dynamic(get_issue_ids);
+
+fn get_issue_ids(input: &str) -> Option<Vec<String>> {
+ let profile = term::profile().ok()?;
+
+ let (_, rid) = radicle::rad::cwd().ok()?;
+ let repo = profile.storage.repository_mut(rid).ok()?;
+ let issues = Issues::open(&repo).ok()?;
+
+ let completions = issues.all().ok()?
+ .filter_map(|issue| {
+ if let Ok((id, _)) = issue {
+ let id = id.to_string();
+ if id.starts_with(input) {
+ return Some(String::from(id.split_at(8).0));
+ }
+ }
+ None
+ })
+ .collect::<Vec<_>>();
+
+ Some(completions)
+}
+
+pub fn completer(args: Vec<OsString>) -> () {
+ let cmd = CliArgs::command();
+ let mut cmd = CompleteCommand::augment_subcommands(cmd);
+ let matches = cmd.clone().get_matches();
+
+ if let Ok(completions) = CompleteCommand::from_arg_matches(&matches) {
+ completions.complete(&mut cmd);
+ } else {
+ dbg!("COMPLETER FAILED: args: {:?}, matches: {:?}", args, matches);
+ }
+}
+
+pub fn to_issue_options() -> anyhow::Result<rad_issue::Options> {
+ let args = CliArgs::parse();
+
+ let options = match args {
+ CliArgs { command } => match command {
+ Some(Commands::Issue(IssueArgs {
+ command: subcommand,
+ })) => match subcommand {
+ Some(IssueCommands::List) => Some(rad_issue::Options {
+ op: rad_issue::Operation::List {
+ assigned: None,
+ state: None,
+ },
+ announce: true,
+ quiet: false,
+ }),
+ Some(IssueCommands::Show { id }) => Some(rad_issue::Options {
+ op: rad_issue::Operation::Show {
+ id: Rev::from(id),
+ format: term::issue::Format::Full,
+ debug: false,
+ },
+ announce: true,
+ quiet: false,
+ }),
+ _ => None,
+ },
+ _ => None,
+ },
+ };
+
+ options.ok_or(anyhow!("Command not implemented FIXME!"))
+}
diff --git a/radicle-cli/src/lib.rs b/radicle-cli/src/lib.rs
index 32019151d..f71f7c9cb 100644
--- a/radicle-cli/src/lib.rs
+++ b/radicle-cli/src/lib.rs
@@ -7,3 +7,4 @@ pub mod node;
pub mod pager;
pub mod project;
pub mod terminal;
+pub mod cli;
diff --git a/radicle-cli/src/main.rs b/radicle-cli/src/main.rs
index ec843acc1..04cb1bae0 100644
--- a/radicle-cli/src/main.rs
+++ b/radicle-cli/src/main.rs
@@ -7,6 +7,7 @@ use anyhow::anyhow;
use radicle::version::Version;
use radicle_cli::commands::*;
use radicle_cli::terminal as term;
+use radicle_cli::cli;
pub const NAME: &str = "rad";
pub const PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
@@ -225,11 +226,11 @@ fn run_other(exe: &str, args: &[OsString]) -> Result<(), Option<anyhow::Error>>
);
}
"issue" => {
- term::run_command_args::<rad_issue::Options, _>(
- rad_issue::HELP,
- rad_issue::run,
- args.to_vec(),
- );
+ let options = cli::to_issue_options()?;
+ rad_issue::run(options, term::profile()?)?;
+ }
+ "complete" => {
+ cli::completer(args.to_vec());
}
"ls" => {
term::run_command_args::<rad_ls::Options, _>(rad_ls::HELP, rad_ls::run, args.to_vec());
Exit code: 0
shell: 'cargo --version rustc --version cargo fmt --check cargo clippy --all-targets --workspace -- --deny clippy::all cargo build --all-targets --workspace cargo doc --workspace cargo test --workspace --no-fail-fast '
Commands:
$ podman run --name 6e14a461-861c-4eaa-9f4f-26ea3c087f3c -v /opt/radcis/ci.rad.levitte.org/cci/state/6e14a461-861c-4eaa-9f4f-26ea3c087f3c/s:/6e14a461-861c-4eaa-9f4f-26ea3c087f3c/s:ro -v /opt/radcis/ci.rad.levitte.org/cci/state/6e14a461-861c-4eaa-9f4f-26ea3c087f3c/w:/6e14a461-861c-4eaa-9f4f-26ea3c087f3c/w -w /6e14a461-861c-4eaa-9f4f-26ea3c087f3c/w -v /opt/radcis/ci.rad.levitte.org/.radicle:/${id}/.radicle:ro -e RAD_HOME=/${id}/.radicle rust:trixie bash /6e14a461-861c-4eaa-9f4f-26ea3c087f3c/s/script.sh
+ cargo --version
info: syncing channel updates for '1.77-x86_64-unknown-linux-gnu'
info: latest update on 2024-04-09, rust version 1.77.2 (25ef9e3d8 2024-04-09)
info: downloading component 'cargo'
info: downloading component 'rust-std'
info: downloading component 'rustc'
info: installing component 'cargo'
info: installing component 'rust-std'
info: installing component 'rustc'
cargo 1.77.2 (e52e36006 2024-03-26)
+ rustc --version
rustc 1.77.2 (25ef9e3d8 2024-04-09)
+ cargo fmt --check
error: 'cargo-fmt' is not installed for the toolchain '1.77-x86_64-unknown-linux-gnu'.
To install, run `rustup component add --toolchain 1.77-x86_64-unknown-linux-gnu rustfmt`
Exit code: 1
{
"response": "finished",
"result": "failure"
}