git-ents.gitmain
⌘K
foforge
commit 233698e
refactor: migrate CLI parsing from clap to figue

Assisted-by: Claude:claude-sonnet-5

Joseph D. Carpinelli · 1 month ago

Reviews

No reviews of this commit yet — record a verdict below.

Start a review

verdict

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)); }