refactor: migrate CLI parsing from clap to figue
commit
233698erefactor: migrate CLI parsing from clap to figue
Assisted-by: Claude:claude-sonnet-5
Reviews
No reviews of this commit yet — record a verdict below.
Start a review
Cargo.lock
@@ -433,6 +433,16 @@
"rustversion",
]
+[[package]]
+name = "ariadne"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8454c8a44ce2cb9cc7e7fae67fc6128465b343b92c6631e94beca3c8d1524ea5"
+dependencies = [
+ "unicode-width",
+ "yansi",
+]
+
[[package]]
name = "arrayvec"
version = "0.7.7"
@@ -661,6 +671,12 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49e78e506b9d7633710dab98996f22f95f3d0f488e8f1aa162830556ed9fc14d"
+[[package]]
+name = "camino"
+version = "1.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f2d30e4173c4026932d51d31d6b0613b1fd3014bf3f9f8943d4ba139c437ba0"
+
[[package]]
name = "cc"
version = "1.2.65"
@@ -683,6 +699,12 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
+[[package]]
+name = "cfg_aliases"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+
[[package]]
name = "chrono"
version = "0.4.45"
@@ -807,6 +829,12 @@
"unicode-segmentation",
]
+[[package]]
+name = "coolor"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "980c2afde4af43d6a05c5be738f9eae595cff86dce1f38f88b95058a98c027f3"
+
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
@@ -1111,9 +1139,9 @@
[[package]]
name = "facet"
-version = "0.50.0-rc.1"
+version = "0.50.0-rc.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f200a48c8c3a5e394716595e12c6e886fdde8f08933ebf12161f559033ac34b5"
+checksum = "cd3e58a40571ddd336865ec9643ea43d5cf93c35ae4f5ad5d903dc4e427a2e03"
dependencies = [
"autocfg",
"facet-core",
@@ -1123,14 +1151,48 @@
[[package]]
name = "facet-core"
-version = "0.50.0-rc.1"
+version = "0.50.0-rc.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b6c4781319707e4c2c222d9d8ebd3f139f87b0592e3ee4ea959ae4f148064db"
+checksum = "a2c2ad271d54e76358f179bc5f75483c4ed025d1009a8599f299ac7bee99dbdd"
dependencies = [
"autocfg",
+ "camino",
"const-fnv1a-hash",
"iddqd",
"impls",
+ "indexmap",
+]
+
+[[package]]
+name = "facet-dessert"
+version = "0.50.0-rc.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39aaece2ca1e5aad3e2bee0f4fea23ae0a11eb5a0b88bb265ec09c39d580aac5"
+dependencies = [
+ "facet-core",
+ "facet-reflect",
+]
+
+[[package]]
+name = "facet-error"
+version = "0.50.0-rc.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "175f8aab2cbd354a829214f7f033c5dff59c60b66848fc833116a4dabcc7f7c6"
+dependencies = [
+ "facet",
+]
+
+[[package]]
+name = "facet-format"
+version = "0.50.0-rc.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64c612924dc3d45853f81e9cfafc513f3ec0be5982f943a7945f49f2492effb1"
+dependencies = [
+ "facet-core",
+ "facet-dessert",
+ "facet-path",
+ "facet-reflect",
+ "facet-solver",
]
[[package]]
@@ -1145,10 +1207,23 @@
]
[[package]]
-name = "facet-macro-parse"
-version = "0.50.0-rc.1"
+name = "facet-json"
+version = "0.50.0-rc.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc584827fce6ab10c1d472f99331c03249f9f9a2c284ffbc30f9e24c78f3cfa0"
+checksum = "82902c758e29af6599f852dd783206697fd33383fa499b07bdcddbd8eb1ef4d0"
+dependencies = [
+ "facet",
+ "facet-core",
+ "facet-format",
+ "facet-reflect",
+ "weavy",
+]
+
+[[package]]
+name = "facet-macro-parse"
+version = "0.50.0-rc.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a43cd88c3df76b7606194a059973aea078940de8cc442dca7c327f51bbc014"
dependencies = [
"facet-macro-types",
"proc-macro2",
@@ -1157,9 +1232,9 @@
[[package]]
name = "facet-macro-types"
-version = "0.50.0-rc.1"
+version = "0.50.0-rc.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a6b438c050eb6498e36fb19291a9f4b6895f94d76e8c221bc123a1c8ea30940"
+checksum = "7dce57ed093067e4a3a1ab42c8c84eec1a25af7726d43a22e390734b29b947ec"
dependencies = [
"proc-macro2",
"quote",
@@ -1168,18 +1243,18 @@
[[package]]
name = "facet-macros"
-version = "0.50.0-rc.1"
+version = "0.50.0-rc.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb7fd8ac402cd765f16332abd1c1a54b228cced0544f9df38febd75bc065842c"
+checksum = "6ae8b8eb7169a41245f31bda2912a3c7d33e0efa7269c0c8847b54377e31ff68"
dependencies = [
"facet-macros-impl",
]
[[package]]
name = "facet-macros-impl"
-version = "0.50.0-rc.1"
+version = "0.50.0-rc.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e9b754436e7ffef7b11df7c4d6a84ae41b75a15f20bc0833a0fdb211e1d78ad6"
+checksum = "53d7d66574a47f07e87058ce6e429a7be58fd77c7686e572cb1f85d8a6a840c6"
dependencies = [
"facet-macro-parse",
"facet-macro-types",
@@ -1191,18 +1266,30 @@
[[package]]
name = "facet-path"
-version = "0.50.0-rc.1"
+version = "0.50.0-rc.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9436ad11339cfb94fa4c858da32834b5a8a00d81aa593dfd1c356db6f0198873"
+checksum = "3a21c558bfb27bf957ead130a3b7e5ea2dee7ccbfb31a524053f061efa164d6f"
dependencies = [
"facet-core",
]
[[package]]
-name = "facet-reflect"
-version = "0.50.0-rc.1"
+name = "facet-pretty"
+version = "0.50.0-rc.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0600129f210fbb325cfd8c1e6b4ef6eab5e5ab92e3b4a53d23c58a3cca39352"
+checksum = "2dbcf41fe256543a1cdc8a75b3ef130100fbe4a7e180b20472d5601e03349d5b"
+dependencies = [
+ "facet-core",
+ "facet-reflect",
+ "owo-colors",
+ "terminal-light",
+]
+
+[[package]]
+name = "facet-reflect"
+version = "0.50.0-rc.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8486d2a82dcb2a1bdca472c2cdc513d0feacda3af2ffd50fb9e04f8dedda8735"
dependencies = [
"facet-core",
"facet-path",
@@ -1210,6 +1297,17 @@
"smallvec 2.0.0-alpha.12",
]
+[[package]]
+name = "facet-solver"
+version = "0.50.0-rc.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5c800eb08ef5e1f18ffafcddf40da5d9ea3c86b8f0486f6ee077b0a3d497812"
+dependencies = [
+ "facet-core",
+ "facet-reflect",
+ "strsim",
+]
+
[[package]]
name = "faster-hex"
version = "0.10.0"
@@ -1226,6 +1324,41 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
+[[package]]
+name = "figue"
+version = "5.0.0-rc.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d683110c41f61f23e580c2f11887cfd2667a75388dbbc551b0fec025d3f6aaa8"
+dependencies = [
+ "ariadne",
+ "camino",
+ "facet",
+ "facet-core",
+ "facet-error",
+ "facet-format",
+ "facet-json",
+ "facet-pretty",
+ "facet-reflect",
+ "figue-attrs",
+ "heck",
+ "indexmap",
+ "owo-colors",
+ "strip-ansi-escapes",
+ "strsim",
+ "supports-color 3.0.2",
+ "tracing",
+ "unicode-width",
+]
+
+[[package]]
+name = "figue-attrs"
+version = "5.0.0-rc.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfb26d9b503638fe61b04ef7d76d0d53f39993549948c0267e05094185b6b808"
+dependencies = [
+ "facet",
+]
+
[[package]]
name = "filedescriptor"
version = "0.8.3"
@@ -1409,9 +1542,9 @@
name = "git-ents"
version = "0.0.0"
dependencies = [
- "clap",
"crossterm",
"facet",
+ "figue",
"form_urlencoded",
"futures-util",
"git-anchor",
@@ -2423,6 +2556,12 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+[[package]]
+name = "hermit-abi"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
+
[[package]]
name = "http"
version = "1.4.2"
@@ -2708,6 +2847,23 @@
"winapi",
]
+[[package]]
+name = "is-terminal"
+version = "0.4.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "is_ci"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45"
+
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
@@ -2954,7 +3110,19 @@
dependencies = [
"bitflags 2.13.0",
"cfg-if",
- "cfg_aliases",
+ "cfg_aliases 0.1.1",
+ "libc",
+]
+
+[[package]]
+name = "nix"
+version = "0.31.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
+dependencies = [
+ "bitflags 2.13.0",
+ "cfg-if",
+ "cfg_aliases 0.2.1",
"libc",
]
@@ -3023,6 +3191,16 @@
"pkg-config",
]
+[[package]]
+name = "owo-colors"
+version = "4.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d"
+dependencies = [
+ "supports-color 2.1.0",
+ "supports-color 3.0.2",
+]
+
[[package]]
name = "parking_lot"
version = "0.12.5"
@@ -3132,7 +3310,7 @@
"lazy_static",
"libc",
"log",
- "nix",
+ "nix 0.28.0",
"serial2",
"shared_library",
"shell-words",
@@ -3735,6 +3913,15 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520"
+[[package]]
+name = "strip-ansi-escapes"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025"
+dependencies = [
+ "vte",
+]
+
[[package]]
name = "strsim"
version = "0.11.1"
@@ -3747,6 +3934,25 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+[[package]]
+name = "supports-color"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6398cde53adc3c4557306a96ce67b302968513830a77a95b2b17305d9719a89"
+dependencies = [
+ "is-terminal",
+ "is_ci",
+]
+
+[[package]]
+name = "supports-color"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6"
+dependencies = [
+ "is_ci",
+]
+
[[package]]
name = "syn"
version = "2.0.118"
@@ -3809,6 +4015,18 @@
"windows-sys 0.61.2",
]
+[[package]]
+name = "terminal-light"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79307346b35bf24ed47c895c71f24653fab51c37576c7df7b5efdb8d519aee6d"
+dependencies = [
+ "coolor",
+ "crossterm",
+ "thiserror 1.0.69",
+ "xterm-query",
+]
+
[[package]]
name = "thiserror"
version = "1.0.69"
@@ -4298,6 +4516,12 @@
"unicode-ident",
]
+[[package]]
+name = "weavy"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30261027ea4225cf7c8287aa70bd103c1e78508606c77caf24bb468326ce7b5f"
+
[[package]]
name = "webpki-roots"
version = "0.26.11"
@@ -4415,6 +4639,15 @@
"windows-targets",
]
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets",
+]
+
[[package]]
name = "windows-sys"
version = "0.61.2"
@@ -4518,6 +4751,17 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
+[[package]]
+name = "xterm-query"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e468ba3129b3d5acd2655410cf12adccd818746e2b8d7fd4b2d22a6e3515ec98"
+dependencies = [
+ "nix 0.31.3",
+ "thiserror 1.0.69",
+ "windows-sys 0.59.0",
+]
+
[[package]]
name = "yaml-rust"
version = "0.4.5"
@@ -4527,6 +4771,12 @@
"linked-hash-map",
]
+[[package]]
+name = "yansi"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
+
[[package]]
name = "yoke"
version = "0.8.3"
Cargo.toml
@@ -44,6 +44,7 @@
axum = "0.8"
clap = { version = "4.5.60", features = ["derive"] }
clap_mangen = "0.2.31"
+figue = "5.0.0-rc.5"
facet = { version = "0.50.0-rc.0", features = ["reflect"] }
getrandom = "0.4"
facet-git-tree = { git = "https://github.com/git-ents/facet-git-tree" }
crates/git-ents/Cargo.toml
@@ -6,9 +6,9 @@
license.workspace = true
[dependencies]
-clap = { workspace = true }
crossterm = "0.29.0"
facet = { workspace = true }
+figue = { workspace = true }
form_urlencoded.workspace = true
futures-util = { version = "0.3.32", default-features = false, features = ["sink", "std"] }
git-anchor = { workspace = true }
crates/git-ents/src/main.rs
@@ -16,7 +16,8 @@
use std::path::{Path, PathBuf};
use std::process::{Command, ExitCode, Stdio};
-use clap::{Parser, Subcommand};
+use facet::Facet;
+use figue::{self as args, FigueBuiltins};
use git_anchor::{LineRange, Projection};
use git_comment::{COMMENTS_NS, Comment};
use git_ents::account::{self, Account};
@@ -24,270 +25,264 @@
use git_ents::members::{self, MEMBER_NS, Member, Trust, member_ref};
use git_ents::revocations::{self, REVOKED_REF, Revocation};
-#[derive(Parser)]
-#[command(name = "git-ents", about = "Helpful guardians of your git trees.")]
+/// Helpful guardians of your git trees.
+#[derive(Facet)]
struct Cli {
- #[command(subcommand)]
+ /// Remote whose refs to operate on.
+ #[facet(args::named, args::short = 'r', default = "origin")]
+ remote: String,
+ #[facet(args::subcommand)]
command: Top,
+ #[facet(flatten)]
+ builtins: FigueBuiltins,
}
-#[derive(Subcommand)]
+#[derive(Facet)]
+#[repr(u8)]
enum Top {
/// Manage the repository members at `refs/meta/member/<username>`.
Members {
- #[command(subcommand)]
+ #[facet(args::subcommand)]
action: Action,
},
/// Manage this repository's account identity at `refs/meta/account`.
Account {
- #[command(subcommand)]
+ #[facet(args::subcommand)]
action: AccountAction,
},
/// Manage the configured checks at `refs/meta/checks`.
Checks {
- #[command(subcommand)]
+ #[facet(args::subcommand)]
action: ChecksAction,
},
/// Comment on code: one comment per ref at `refs/meta/comments/<id>`,
/// anchored to a blob (and optionally lines) at a commit.
Comment {
- #[command(subcommand)]
+ #[facet(args::subcommand)]
action: CommentAction,
},
/// Sign in to a remote's server the same way the web UI does — sign a
/// server-issued challenge with your key — so this machine can also open a
/// debug session (`checks debug`).
Login {
- /// Remote whose server to sign in to.
- #[arg(default_value = "origin")]
- remote: String,
/// Key to sign in with; defaults to `user.signingkey`.
- #[arg(long)]
+ #[facet(args::named)]
key: Option<PathBuf>,
},
}
-#[derive(Subcommand)]
+#[derive(Facet)]
+#[repr(u8)]
enum Action {
/// Set this machine up to sign the pushes the server requires.
Setup {
/// Key to sign with; defaults to `user.signingkey`, else a new or
/// existing `~/.ssh/id_ed25519`.
- #[arg(long)]
+ #[facet(args::named)]
key: Option<PathBuf>,
/// Write to this repository's config instead of your global config.
- #[arg(long)]
+ #[facet(args::named, default)]
local: bool,
},
/// List the members on a remote.
- List {
- /// Remote to read the `refs/meta/member/*` refs from.
- #[arg(default_value = "origin")]
- remote: String,
- },
+ List,
/// Authorize a key for a member on a remote and push the update. Prompts
/// for any field left unset when run at an interactive terminal.
Add {
/// Member (username) to authorize the key under — its
/// `refs/meta/member/<username>` ref.
+ #[facet(args::positional, default)]
username: Option<String>,
- /// Remote whose member refs to update.
- #[arg(default_value = "origin")]
- remote: String,
/// Key to authorize; defaults to `user.signingkey`.
- #[arg(long)]
+ #[facet(args::named)]
key: Option<PathBuf>,
/// Pin a certificate authority public key instead of leaf keys: trust
- /// any certificate it issues for the member, within the cert's validity.
- #[arg(long, value_name = "CA_PUBKEY", conflicts_with = "key")]
+ /// any certificate it issues for the member, within the cert's
+ /// validity. Conflicts with `--key`.
+ #[facet(args::named, args::label = "CA_PUBKEY")]
cert_authority: Option<PathBuf>,
/// Trust the member only at or after this OpenSSH timestamp
/// (`YYYYMMDD[Z]` or `YYYYMMDDHHMM[SS][Z]`; append `Z` for UTC).
- #[arg(long, value_name = "TIMESTAMP")]
+ #[facet(args::named, args::label = "TIMESTAMP")]
valid_after: Option<String>,
/// Stop trusting the member after this OpenSSH timestamp; omit for trust
/// that never lapses on its own.
- #[arg(long, value_name = "TIMESTAMP")]
+ #[facet(args::named, args::label = "TIMESTAMP")]
valid_before: Option<String>,
/// Link this member to an account by its genesis hash (`git ents
/// account create` prints one).
- #[arg(long, value_name = "GENESIS_HASH")]
+ #[facet(args::named, args::label = "GENESIS_HASH")]
account: Option<String>,
},
/// Remove a member, deleting its ref on a remote and pushing the update.
Remove {
/// Member (username) to remove — its `refs/meta/member/<username>` ref.
+ #[facet(args::positional)]
username: String,
- /// Remote whose member ref to delete.
- #[arg(default_value = "origin")]
- remote: String,
},
/// Revoke a key fast: add its fingerprint to the `refs/meta/revoked` deny
/// list so it is refused before its window expires, and push the update.
Revoke {
/// Fingerprint of the key to deny (as shown by `members list`).
+ #[facet(args::positional)]
fingerprint: String,
- /// Remote whose `refs/meta/revoked` to update.
- #[arg(default_value = "origin")]
- remote: String,
/// Free-text reason recorded alongside the revocation.
- #[arg(long, default_value = "")]
+ #[facet(args::named, default = "")]
reason: String,
},
/// Lift a revocation, removing a fingerprint from the `refs/meta/revoked`
/// deny list and pushing the update.
Unrevoke {
/// Fingerprint to stop denying.
+ #[facet(args::positional)]
fingerprint: String,
- /// Remote whose `refs/meta/revoked` to update.
- #[arg(default_value = "origin")]
- remote: String,
},
/// Report whether a key is a member and the client is configured.
Check {
- /// Remote to read the `refs/meta/member/*` refs from.
- #[arg(default_value = "origin")]
- remote: String,
/// Key to look for; defaults to `user.signingkey`.
- #[arg(long)]
+ #[facet(args::named)]
key: Option<PathBuf>,
},
}
-#[derive(Subcommand)]
+#[derive(Facet)]
+#[repr(u8)]
enum AccountAction {
/// Create or update this repository's account identity and push it. The
/// presence of `refs/meta/account` is what marks the repo as an account.
/// Prompts for any field left unset when run at an interactive terminal.
Create {
/// The account username — by convention the `user/<username>` repo name.
+ #[facet(args::positional, default)]
username: Option<String>,
- /// Remote whose `refs/meta/account` to update.
- #[arg(default_value = "origin")]
- remote: String,
/// Human-facing display name; defaults to the username.
- #[arg(long)]
+ #[facet(args::named)]
display_name: Option<String>,
/// Short free-text bio.
- #[arg(long)]
+ #[facet(args::named)]
bio: Option<String>,
},
}
-#[derive(Subcommand)]
+#[derive(Facet)]
+#[repr(u8)]
enum ChecksAction {
/// List the checks configured on a remote.
- List {
- /// Remote to read `refs/meta/checks` from.
- #[arg(default_value = "origin")]
- remote: String,
- },
+ List,
/// Add (or replace) a check on a remote's set and push the update.
/// Prompts for any field left unset when run at an interactive terminal.
Add {
/// Name to record the check under (`checks/<name>`).
+ #[facet(args::positional, default)]
name: Option<String>,
/// Command the check runs (e.g. `cargo fmt --check`); omit for a
/// composite check that only aggregates its dependencies.
+ #[facet(args::positional, default)]
command: Option<String>,
/// Sandbox image the command runs in (reserved: the Sprite sandbox
/// does not honor an image yet, so setting one is rejected).
- #[arg(long)]
+ #[facet(args::named)]
image: Option<String>,
/// Check that must pass before this one runs (repeatable).
- #[arg(long = "depends", value_name = "CHECK")]
+ #[facet(args::named, args::label = "CHECK", default)]
depends: Vec<String>,
- /// Remote whose `refs/meta/checks` to update.
- #[arg(default_value = "origin")]
- remote: String,
},
/// Remove a check from a remote's set and push the update.
Remove {
/// Name (`checks/<name>`) to drop.
+ #[facet(args::positional)]
name: String,
- /// Remote whose `refs/meta/checks` to update.
- #[arg(default_value = "origin")]
- remote: String,
},
/// Open an interactive, read-write shell in `remote`'s persistent checks
/// Sprite — the same sandbox its check runs execute in. Requires
/// `git ents login <remote>` first.
- Debug {
- /// Remote whose checks Sprite to open a shell in.
- #[arg(default_value = "origin")]
- remote: String,
- },
+ Debug,
/// Show recorded check runs (queued/running/pass/fail/error) from
/// `refs/meta/runs/*` on a remote, newest first.
- Runs {
- /// Remote to read `refs/meta/runs/*` from.
- #[arg(default_value = "origin")]
- remote: String,
- },
+ Runs,
}
-#[derive(Subcommand)]
+#[derive(Facet)]
+#[repr(u8)]
enum CommentAction {
/// Anchor a comment to a file at a revision and push it. Prompts for the
/// path and body when left unset at an interactive terminal.
Add {
/// Repository-relative path of the file the comment anchors to.
+ #[facet(args::positional, default)]
path: Option<String>,
- /// Remote whose comment refs to update.
- #[arg(default_value = "origin")]
- remote: String,
/// The comment's body text.
- #[arg(long)]
+ #[facet(args::named)]
body: Option<String>,
/// Lines to anchor, as `<start>[:<end>]` (1-based, inclusive); omit
/// for a whole-file comment.
- #[arg(long)]
+ #[facet(args::named)]
lines: Option<String>,
/// Revision to anchor against.
- #[arg(long, default_value = "HEAD")]
+ #[facet(args::named, default = "HEAD")]
rev: String,
/// Genesis id of the issue the comment belongs to.
- #[arg(long)]
+ #[facet(args::named)]
issue: Option<String>,
},
/// List the comments on a remote, each projected onto a revision.
List {
- /// Remote to read the `refs/meta/comments/*` refs from.
- #[arg(default_value = "origin")]
- remote: String,
/// Revision to project each comment's anchor onto.
- #[arg(long, default_value = "HEAD")]
+ #[facet(args::named, default = "HEAD")]
rev: String,
},
/// Show one comment: author, anchor, projection, anchored text, and body.
Show {
/// The comment's id (or a unique prefix of it).
+ #[facet(args::positional)]
id: String,
- /// Remote to read the `refs/meta/comments/*` refs from.
- #[arg(default_value = "origin")]
- remote: String,
/// Revision to project the comment's anchor onto.
- #[arg(long, default_value = "HEAD")]
+ #[facet(args::named, default = "HEAD")]
rev: String,
},
/// Remove a comment, deleting its ref on a remote.
Remove {
/// The comment's id (or a unique prefix of it).
+ #[facet(args::positional)]
id: String,
- /// Remote whose comment ref to delete.
- #[arg(default_value = "origin")]
- remote: String,
},
}
fn main() -> ExitCode {
- let cli = Cli::parse();
+ let config = match figue::builder::<Cli>() {
+ Ok(builder) => builder,
+ Err(error) => {
+ eprintln!("{error}");
+ return ExitCode::FAILURE;
+ }
+ }
+ .cli(|cli| cli.args(std::env::args().skip(1)))
+ .help(|help| {
+ help.program_name("git-ents")
+ .version(env!("CARGO_PKG_VERSION"))
+ })
+ .build();
+ let cli: Cli = match figue::Driver::new(config).run().into_result() {
+ Ok(output) => output.get(),
+ Err(figue::DriverError::Help {
+ text,
+ suggestion: suggestion @ Some(_),
+ }) => {
+ println!("{text}");
+ if let Some(s) = suggestion {
+ println!("{}", s.render_pretty());
+ }
+ return ExitCode::FAILURE;
+ }
+ Err(error) => figue::DriverOutcome::<Cli>::err(error).unwrap(),
+ };
+ let remote = cli.remote;
let result = match cli.command {
- Top::Members { action } => run_members(action),
- Top::Account { action } => run_account(action),
- Top::Checks { action } => run_checks(action),
- Top::Comment { action } => run_comment(action),
- Top::Login { remote, key } => login(&remote, key.as_deref()),
+ Top::Members { action } => run_members(action, &remote),
+ Top::Account { action } => run_account(action, &remote),
+ Top::Checks { action } => run_checks(action, &remote),
+ Top::Comment { action } => run_comment(action, &remote),
+ Top::Login { key } => login(&remote, key.as_deref()),
};
match result {
Ok(()) => ExitCode::SUCCESS,
@@ -298,13 +293,12 @@
}
}
-fn run_members(action: Action) -> Result<(), String> {
+fn run_members(action: Action, remote: &str) -> Result<(), String> {
match action {
Action::Setup { key, local } => setup(key.as_deref(), local),
- Action::List { remote } => members_list(&remote),
+ Action::List => members_list(remote),
Action::Add {
username,
- remote,
key,
cert_authority,
valid_after,
@@ -312,51 +306,45 @@
account,
} => members_add(
username,
- &remote,
+ remote,
key,
cert_authority,
valid_after,
valid_before,
account,
),
- Action::Remove { username, remote } => members_remove(&username, &remote),
+ Action::Remove { username } => members_remove(&username, remote),
Action::Revoke {
fingerprint,
- remote,
reason,
- } => members_revoke(&fingerprint, &remote, reason),
- Action::Unrevoke {
- fingerprint,
- remote,
- } => members_unrevoke(&fingerprint, &remote),
- Action::Check { remote, key } => check(&remote, key.as_deref()),
+ } => members_revoke(&fingerprint, remote, reason),
+ Action::Unrevoke { fingerprint } => members_unrevoke(&fingerprint, remote),
+ Action::Check { key } => check(remote, key.as_deref()),
}
}
-fn run_account(action: AccountAction) -> Result<(), String> {
+fn run_account(action: AccountAction, remote: &str) -> Result<(), String> {
match action {
AccountAction::Create {
username,
- remote,
display_name,
bio,
- } => account_create(username, &remote, display_name, bio),
+ } => account_create(username, remote, display_name, bio),
}
}
-fn run_checks(action: ChecksAction) -> Result<(), String> {
+fn run_checks(action: ChecksAction, remote: &str) -> Result<(), String> {
match action {
- ChecksAction::List { remote } => list::<Checks>(&remote),
+ ChecksAction::List => list::<Checks>(remote),
ChecksAction::Add {
name,
command,
image,
depends,
- remote,
- } => add_check(name, command, image, depends, &remote),
- ChecksAction::Remove { name, remote } => remove::<Checks>(&name, &remote),
- ChecksAction::Debug { remote } => checks_debug(&remote),
- ChecksAction::Runs { remote } => checks_runs(&remote),
+ } => add_check(name, command, image, depends, remote),
+ ChecksAction::Remove { name } => remove::<Checks>(&name, remote),
+ ChecksAction::Debug => checks_debug(remote),
+ ChecksAction::Runs => checks_runs(remote),
}
}
@@ -388,19 +376,18 @@
Ok(())
}
-fn run_comment(action: CommentAction) -> Result<(), String> {
+fn run_comment(action: CommentAction, remote: &str) -> Result<(), String> {
match action {
CommentAction::Add {
path,
- remote,
body,
lines,
rev,
issue,
- } => comment_add(path, body, lines.as_deref(), &rev, issue, &remote),
- CommentAction::List { remote, rev } => comment_list(&remote, &rev),
- CommentAction::Show { id, remote, rev } => comment_show(&id, &remote, &rev),
- CommentAction::Remove { id, remote } => comment_remove(&id, &remote),
+ } => comment_add(path, body, lines.as_deref(), &rev, issue, remote),
+ CommentAction::List { rev } => comment_list(remote, &rev),
+ CommentAction::Show { id, rev } => comment_show(&id, remote, &rev),
+ CommentAction::Remove { id } => comment_remove(&id, remote),
}
}
@@ -1007,6 +994,9 @@
key: Option<PathBuf>,
cert_authority: Option<PathBuf>,
) -> Result<(Option<PathBuf>, Option<PathBuf>), String> {
+ if key.is_some() && cert_authority.is_some() {
+ return Err("--key conflicts with --cert-authority".to_string());
+ }
if key.is_some() || cert_authority.is_some() || !interactive::available() {
return Ok((key, cert_authority));
}