From ec84e86659c86b6d98df922f9bf7bfa5879fc6bc Mon Sep 17 00:00:00 2001 From: kalmenn Date: Mon, 2 Feb 2026 09:30:56 +0100 Subject: [PATCH] read from an instance of GnuPG --- Cargo.lock | 859 ++++++++++++++++++++++++++++++++++- Cargo.toml | 18 +- TODO.md | 21 +- src/cli.rs | 22 +- src/lib.rs | 8 + src/subcommands/import.rs | 365 +++++++++++++++ src/subcommands/mod.rs | 2 +- src/subcommands/say_hello.rs | 7 - 8 files changed, 1267 insertions(+), 35 deletions(-) create mode 100644 src/subcommands/import.rs delete mode 100644 src/subcommands/say_hello.rs diff --git a/Cargo.lock b/Cargo.lock index 2827f7c..eab7eb8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.21" @@ -47,7 +56,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -58,7 +67,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -67,12 +76,75 @@ version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "build-rs" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00b8763668c99f8d9101b8a0dd82106f58265464531a79b2cef0d9a30c17dd2" + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "cc" +version = "1.2.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "chrono" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + [[package]] name = "clap" version = "4.5.56" @@ -129,35 +201,326 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "console" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e45a4a8926227e4197636ba97a9fc9b00477e9f4bd711395687c5f0734bec4" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width", + "windows-sys 0.61.2", +] + +[[package]] +name = "conv" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ff10625fd0ac447827aa30ea8b861fead473bb60aeb73af6c1c58caf0d1299" +dependencies = [ + "custom_derive", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cstr-argument" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bd9c8e659a473bce955ae5c35b116af38af11a7acb0b480e01f3ed348aeb40" +dependencies = [ + "cfg-if", + "memchr", +] + +[[package]] +name = "custom_derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" + +[[package]] +name = "dialoguer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25f104b501bf2364e78d0d3974cbc774f738f5865306ed128e1e0d7499c0ad96" +dependencies = [ + "console", + "fuzzy-matcher", + "shell-words", + "tempfile", + "zeroize", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.2", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fuzzy-matcher" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" +dependencies = [ + "thread_local", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + [[package]] name = "git-identity" version = "0.1.0" dependencies = [ "anyhow", + "chrono", "clap", + "console", + "dialoguer", + "dirs", + "gpgme", + "itertools", + "serde", "thiserror", + "toml 0.9.11+spec-1.1.0", "tracing", "tracing-subscriber", ] +[[package]] +name = "gpg-error" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "545aae14d0e95734d639c8076304e6e86de765c19c76bead3648583d9caed919" +dependencies = [ + "libgpg-error-sys", +] + +[[package]] +name = "gpgme" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57539732fbe58eacdb984734b72b470ed0bca3ab7a49813271878567025ac44f" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "conv", + "cstr-argument", + "gpg-error", + "gpgme-sys", + "libc", + "memoffset", + "once_cell", + "smallvec", + "static_assertions", +] + +[[package]] +name = "gpgme-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "509223d659c06e4a26229437d6ac917723f02d31917c86c6ecd50e8369741cf7" +dependencies = [ + "build-rs", + "libc", + "libgpg-error-sys", + "system-deps", + "winreg 0.10.1", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "libgpg-error-sys" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "500a4cbc0816ed820a5bcf73a19e74dd6df4bedeabc0f64471c61186938b6c82" +dependencies = [ + "build-rs", + "system-deps", + "winreg 0.52.0", +] + +[[package]] +name = "libredox" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +dependencies = [ + "bitflags 2.10.0", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + [[package]] name = "log" version = "0.4.29" @@ -179,13 +542,31 @@ version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + [[package]] name = "nu-ansi-term" version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", ] [[package]] @@ -200,12 +581,24 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + [[package]] name = "proc-macro2" version = "1.0.106" @@ -224,6 +617,23 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror", +] + [[package]] name = "regex-automata" version = "0.4.13" @@ -247,6 +657,73 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3" +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +dependencies = [ + "serde_core", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -256,12 +733,30 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shell-words" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.11.1" @@ -279,6 +774,38 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml 0.8.23", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tempfile" +version = "3.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + [[package]] name = "thiserror" version = "2.0.18" @@ -308,6 +835,79 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit", +] + +[[package]] +name = "toml" +version = "0.9.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned 1.0.4", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" + [[package]] name = "tracing" version = "0.1.44" @@ -375,6 +975,12 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + [[package]] name = "utf8parse" version = "0.2.2" @@ -387,12 +993,162 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -402,6 +1158,97 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" + [[package]] name = "xtask" version = "0.1.0" @@ -411,3 +1258,9 @@ dependencies = [ "clap_mangen", "git-identity", ] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" diff --git a/Cargo.toml b/Cargo.toml index c9c46a7..3bd09c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,8 +7,8 @@ edition = "2024" license = "MIT OR Apache-2.0" [workspace.dependencies] -anyhow = "1.0.100" -clap = { version = "4.5.56", features = ["derive"] } +anyhow = "1.0" +clap = { version = "4.5", features = ["derive"] } [package] name = "git-identity" @@ -18,10 +18,18 @@ license.workspace = true [dependencies] anyhow.workspace = true +chrono = "0.4.43" clap.workspace = true -thiserror = "2.0.18" -tracing = "0.1.44" -tracing-subscriber = { version = "0.3.22", features = ["env-filter"] } +console = "0.16" +dialoguer = { version = "0.12", features = ["fuzzy-select"] } +dirs = "6.0" +gpgme = "0.11" +itertools = "0.14.0" +serde = { version = "1.0", features = ["derive"] } +thiserror = "2.0" +toml = "0.9" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } [profile.release] lto = true diff --git a/TODO.md b/TODO.md index cff1c89..63f10ba 100644 --- a/TODO.md +++ b/TODO.md @@ -3,17 +3,7 @@ ## import Import an identity from a gpg key. -Relevant crates: -```Cargo.toml -# For finding the cache directory in which to store the SSH keys exported from GPG and identities -dirs = "6.0.0" -# For importing GPG keys -gpgme = "0.11.0" - -# For reading/writing identities' files -serde = { version = "1.0.228", features = ["derive"] } -toml = "0.9.11" -``` +We still need to save it somewhere ## use Set the identity of the current git repo @@ -37,9 +27,12 @@ List all saved identities Interactively edit a saved identity (or in a text editor when using a specific flag) # User input -[inquire](https://docs.rs/inquire/latest/inquire/) seems simple enough. But -[dialoguer](https://docs.rs/dialoguer/latest/dialoguer/) is the one use by tauri which seems much -more complete. +Currently, dialoguer (which uses the console crate) doesn't reset the terminal state properly when +the user exists using ctrl-c. Most importantly, the cursor remains hidden even after the program +exited. +Maybe switching to [inquire](https://docs.rs/inquire/latest/inquire/) would fix this. If not, we +might need to check if console emits `std::io::ErrorKind::Interrupted` like it seems to me it does +and whether or not we can react to it easily. # Completions clap_complete does that statically. It will be able to do it dynamically once the diff --git a/src/cli.rs b/src/cli.rs index 51aa779..c228738 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,6 +1,10 @@ //! The Command Line Interface definition to the git-identity subcommand +use std::{borrow::Cow, sync::LazyLock}; + use clap::{ArgAction, Args, Parser, Subcommand}; +use console::Term; +use dialoguer::theme::ColorfulTheme; /// Manages multiple identities (e.g. personal, work, university, ...) in git repos for you. #[derive(Parser, Debug)] @@ -15,7 +19,7 @@ pub struct Cli { impl Cli { pub fn run_subcommand(self) -> anyhow::Result<()> { match self.command { - Command::SayHello(cli) => crate::subcommands::say_hello::main(self.global, cli), + Command::Import(cli) => crate::subcommands::import::main(self.global, cli), } } } @@ -32,11 +36,19 @@ pub struct GlobalArgs { #[derive(Subcommand, Debug)] pub enum Command { - SayHello(SayHelloCli), + Import(ImportCli), } -/// A temporary subcommand for testing the cli #[derive(Parser, Debug)] -pub struct SayHelloCli { - pub name: String, +pub struct ImportCli { + /// A list of patterns to filter GPG keys with, works the same way as patterns you use with + /// the gpg command + #[arg(id = "PATTERN")] + pub patterns: Vec>, } + +pub static THEME: LazyLock = LazyLock::new(|| ColorfulTheme { + ..Default::default() +}); + +pub static STDERR: LazyLock = LazyLock::new(Term::stderr); diff --git a/src/lib.rs b/src/lib.rs index 8e40229..c7c54a3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,14 @@ //! This crate is not a library but we need some things to be exported so that they can be read by //! cargo xtasks +use std::{path::PathBuf, sync::LazyLock}; + pub mod cli; pub mod logging; pub mod subcommands; + +pub static DATA_FOLDER: LazyLock = LazyLock::new(|| { + let mut dir = dirs::data_dir().expect(""); + dir.push("git-identity"); + dir +}); diff --git a/src/subcommands/import.rs b/src/subcommands/import.rs new file mode 100644 index 0000000..68470ae --- /dev/null +++ b/src/subcommands/import.rs @@ -0,0 +1,365 @@ +use std::{ + borrow::Cow, + ffi::{CStr, CString}, + fs::File, + path::{Path, PathBuf}, + sync::LazyLock, + time::{Duration, SystemTime}, +}; + +use anyhow::Context as _; +use dialoguer::theme::Theme as _; +use gpgme::Context; +use tracing::{error, info, warn}; + +use crate::cli::{STDERR, THEME}; + +pub fn main(_global_cli: crate::cli::GlobalArgs, cli: crate::cli::ImportCli) -> anyhow::Result<()> { + let mut ctx = gpgme::Context::from_protocol(gpgme::Protocol::OpenPgp)?; + + let key = select_key(&mut ctx, cli.patterns.iter().map(Cow::as_ref))?; + + let fingerprint = key + .fingerprint_raw() + .context("querying the fingerprint of your key")? + .to_str() + .expect("this should always be valid UTF-8"); + + let identity_name = dialoguer::Input::::with_theme(&*THEME) + .with_prompt("What should this identity be named?") + .interact_text()?; + + let authkey: Option = select_authkey(&mut ctx, &key, &identity_name)?; + + let (name, email) = select_name_and_email(&key)?; + + info!( + "Imported identity: [{identity_name}] {name:?} <{email:?}> (signing key: {fingerprint}, authentication key: {authkey:?})" + ); + + Ok(()) +} + +fn select_key

(ctx: &mut gpgme::Context, patterns: P) -> anyhow::Result +where + P: IntoIterator, + Vec: From<

::Item>, +{ + let patterns: Vec = patterns + .into_iter() + .map(|p| Ok(CString::new(p)?)) + .collect::>() + .context("Converting patterns to CStrings")?; + + let mut keys = ctx + .find_secret_keys(patterns.iter())? + .inspect(|it| { + if let Err(err) = it { + error!("failed to query information about a key. Got err: {err}"); + } + }) + .filter_map(Result::ok) + .collect::>(); + + match keys.len() { + 0 => { + STDERR.write_line("Found no matching key")?; + std::process::exit(1); + } + 1 => return Ok(keys.pop().expect("Vec is non empty")), + _ => (), + } + + // There is some weird behavior when using FuzzySelect with items spanning multiple lines + STDERR.clear_screen()?; + + let index = dialoguer::FuzzySelect::with_theme(&*THEME) + .with_prompt("Select a GPG key to import") + .items(keys.iter().map(format_key_for_select)) + .highlight_matches(false) + .interact()?; + + Ok(keys.swap_remove(index)) +} + +fn format_key_for_select(key: &gpgme::Key) -> Cow<'static, str> { + let fingerprint: Cow = key + .fingerprint_raw() + .map(CStr::to_string_lossy) + .unwrap_or(Cow::Borrowed(&FINGERPRINT_ERRSTR)); + + let primary_key: Cow = key + .primary_key() + .and_then(|key| format_subkey(key, true)) + .unwrap_or(Cow::Borrowed(&PRIMARY_KEY_ERRSTR)); + + let user_ids: String = itertools::intersperse( + key.user_ids() + .map(|uid| Cow::Owned(format!(" uid {}", format_user_id(&uid)))), + Cow::Borrowed("\n"), + ) + .collect(); + + let subkeys = itertools::intersperse( + key.subkeys().map(|key| { + Cow::Owned(format!( + " {}", + format_subkey(key, false).unwrap_or(Cow::Borrowed(&SUBKEY_ERRSTR)) + )) + }), + Cow::Borrowed("\n"), + ) + .collect::(); + + let formatted = format!("{primary_key}\n {fingerprint:40}\n{user_ids}\n{subkeys}\n"); + + Cow::Owned(formatted) +} + +fn format_subkey(key: gpgme::Subkey, is_primary: bool) -> Option> { + let algorithm = key + .algorithm() + .name_raw() + .map(CStr::to_string_lossy) + .unwrap_or(Cow::Borrowed(&ALGORITHM_ERRSTR)); + + let creation_time: Cow = key + .creation_time() + .map(|time| { + let creation_time = chrono::DateTime::UNIX_EPOCH + + time + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap_or(Duration::ZERO); + + let local_creation_time = creation_time.with_timezone(&chrono::Local::now().timezone()); + + let formatted = local_creation_time.format("%Y-%m-%d").to_string(); + + Cow::Owned(formatted) + }) + .unwrap_or(Cow::Borrowed(&CREATION_TIME_ERRSTR)); + + let capabilities = { + let mut caps = String::new(); + + if key.can_sign() { + caps.push('S'); + } + if key.can_encrypt() { + caps.push('E') + } + if key.can_certify() { + caps.push('C'); + } + if key.can_authenticate() { + caps.push('A'); + } + + caps + }; + + let formatted = format!( + "{:3} {algorithm:7} {creation_time} [{capabilities}]", + match (key.is_secret(), is_primary) { + (true, true) => "sec", + (false, true) => "pub", + (true, false) => "ssb", + (false, false) => "syb", + }, + ); + + Some(Cow::Owned(formatted)) +} + +fn format_user_id(uid: &gpgme::UserId) -> String { + let [name, comment, email] = [uid.name_raw(), uid.comment_raw(), uid.email_raw()] + .map(|attr| attr.map(CStr::to_string_lossy).unwrap_or(Cow::Borrowed(""))); + + format!("{name} ({comment}) <{email}>") +} + +macro_rules! format_error_strings { + ($($name:ident = $val:expr;)+) => { + $( + static $name: LazyLock = LazyLock::new(|| { + let mut out = String::new(); + THEME + .format_error(&mut out, $val) + .expect("format failed"); + out + }); + )* + } +} + +format_error_strings! { + PRIMARY_KEY_ERRSTR = "[failed to query primary key]"; + SUBKEY_ERRSTR = "[failed to query subkey]"; + FINGERPRINT_ERRSTR = "[failed to query fingerprint]"; + ALGORITHM_ERRSTR = "[failed to query algorithm]"; + CREATION_TIME_ERRSTR = "[failed to query creation time]"; +} + +fn select_authkey( + ctx: &mut gpgme::Context, + key: &gpgme::Key, + identity_name: &str, +) -> anyhow::Result> { + let mut authkey_prompt = dialoguer::Input::::with_theme(&*THEME) + .with_prompt("Select an SSH key (leave empty for none)") + .allow_empty(true); + + if let Some(authkey) = try_export_as_ssh_key(ctx, key, identity_name)? { + let default_authkey = authkey.to_string_lossy(); + + if default_authkey.contains(char::REPLACEMENT_CHARACTER) { + warn!( + "the path to the key you just imported contains non UTF-8 characters. They will be lost if you enter it as is" + ); + } + + authkey_prompt = authkey_prompt.with_initial_text(default_authkey); + } + + let authkey = authkey_prompt.interact_text()?; + + Ok(if authkey.is_empty() { + None + } else { + Some(PathBuf::from(authkey)) + }) +} + +/// Checks if the key has authentication capabilities then handles exporting is as an SSH key +/// according to the user's input. +fn try_export_as_ssh_key( + ctx: &mut gpgme::Context, + key: &gpgme::Key, + identity_name: &str, +) -> anyhow::Result> { + if !key.can_authenticate() + || !dialoguer::Confirm::with_theme(&*THEME) + .with_prompt( + "This key can authenticate. Do you want to export it as an SSH key for later?", + ) + .interact()? + { + return Ok(None); + } + + let default_destination = { + let mut path = crate::DATA_FOLDER.clone(); + path.push("ssh"); + path.push(format!("{identity_name}.pub")); + path + }; + + let destination: PathBuf = dialoguer::Input::::with_theme(&*THEME) + .with_prompt("Save as") + // FIXME: we are screwed if for example the linux user's name contains non valid UTF-8 + .with_initial_text(default_destination.to_string_lossy()) + .interact_text()? + .into(); + + let fingerprint = key + .fingerprint_raw() + .context("querying your key's fingerprint")?; + + save_ssh_key(ctx, fingerprint, &destination)?; + + info!("succesfully saved your SSH key to disk"); + + Ok(Some(destination)) +} + +fn save_ssh_key>( + ctx: &mut Context, + fingerprint: &CStr, + path: P, +) -> anyhow::Result<()> { + if std::fs::exists(&path).context("Checking if file already exists")? + && !dialoguer::Confirm::with_theme(&*THEME) + .with_prompt(format!( + "{} already exists. Overwrite?", + path.as_ref().display() + )) + .interact()? + { + return Ok(()); + } + + let parent = { + let mut path = PathBuf::from(path.as_ref()); + path.pop(); + path + }; + std::fs::create_dir_all(parent).context("Creating parent directories")?; + + let mut out = File::options() + .create(true) + .write(true) + .truncate(true) + .append(false) + .open(&path)?; + + ctx.export([fingerprint], gpgme::ExportMode::SSH, &mut out) + .context("asking GPG to export your SSH key")?; + + Ok(()) +} + +fn select_name_and_email(key: &gpgme::Key) -> anyhow::Result<(Option, Option)> { + fn select<'key, G>( + key: &'key gpgme::Key, + item_kind: &str, + getter: G, + ) -> anyhow::Result> + where + G: Fn(&gpgme::UserId<'key>) -> Option<&'key CStr>, + { + let mut items_and_uids = key + .user_ids() + .flat_map(|uid| Some((getter(&uid)?, format_user_id(&uid)))) + .collect::>(); + + let picked: Option = if items_and_uids.is_empty() { + None + } else { + let choice = dialoguer::FuzzySelect::with_theme(&*THEME) + .with_prompt(format!( + "From which user ID would you like to import your {item_kind}?" + )) + .items( + std::iter::once("None (enter my own or leave empty)") + .chain(items_and_uids.iter().map(|pair| pair.1.as_str())), + ) + .interact()?; + + if choice == 0 { + None + } else { + let out = items_and_uids.swap_remove(choice - 1).0; + drop(items_and_uids); + Some(out.to_string_lossy().to_string()) + } + }; + + let result = if let Some(item) = picked { + Some(item) + } else { + let input = dialoguer::Input::::with_theme(&*THEME) + .with_prompt(format!("Enter your {item_kind} (leave empty for none)")) + .allow_empty(true) + .interact_text()?; + + if input.is_empty() { None } else { Some(input) } + }; + + Ok(result) + } + + let name = select(key, "name", gpgme::UserId::name_raw)?; + let email = select(key, "email", gpgme::UserId::email_raw)?; + + Ok((name, email)) +} diff --git a/src/subcommands/mod.rs b/src/subcommands/mod.rs index 03bdf6d..07fecca 100644 --- a/src/subcommands/mod.rs +++ b/src/subcommands/mod.rs @@ -1 +1 @@ -pub mod say_hello; +pub mod import; diff --git a/src/subcommands/say_hello.rs b/src/subcommands/say_hello.rs deleted file mode 100644 index a73e459..0000000 --- a/src/subcommands/say_hello.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub fn main( - _global_args: crate::cli::GlobalArgs, - cli: crate::cli::SayHelloCli, -) -> anyhow::Result<()> { - println!("Hello {}!", cli.name); - Ok(()) -}