diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 5008ddf..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore index 0592392..82b32a0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target .DS_Store +Cargo.lock diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 2ade439..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,744 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "addr2line" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "backtrace" -version = "0.3.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e121dee8023ce33ab248d9ce1493df03c3b38a659b240096fcbd7048ff9c31f" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "cc" -version = "1.0.72" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "clap" -version = "4.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335867764ed2de42325fafe6d18b8af74ba97ee0c590fa016f157535b42ab04b" -dependencies = [ - "atty", - "bitflags", - "clap_derive", - "clap_lex", - "once_cell", - "strsim", - "termcolor", -] - -[[package]] -name = "clap_derive" -version = "4.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16a1b0f6422af32d5da0c58e2703320f379216ee70198241c84173a8c5ac28f3" -dependencies = [ - "heck", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" -dependencies = [ - "os_str_bytes", -] - -[[package]] -name = "color-eyre" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f1885697ee8a177096d42f158922251a41973117f6d8a234cee94b9509157b7" -dependencies = [ - "backtrace", - "color-spantrace", - "eyre", - "indenter", - "once_cell", - "owo-colors", - "tracing-error", -] - -[[package]] -name = "color-spantrace" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6eee477a4a8a72f4addd4de416eb56d54bc307b284d6601bafdee1f4ea462d1" -dependencies = [ - "once_cell", - "owo-colors", - "tracing-core", - "tracing-error", -] - -[[package]] -name = "crossterm" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" -dependencies = [ - "bitflags", - "crossterm_winapi", - "libc", - "mio", - "parking_lot", - "signal-hook", - "signal-hook-mio", - "winapi", -] - -[[package]] -name = "crossterm_winapi" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" -dependencies = [ - "winapi", -] - -[[package]] -name = "dyn-clone" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2" - -[[package]] -name = "eyre" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc225d8f637923fe585089fcf03e705c222131232d2c1fb622e84ecf725d0eb8" -dependencies = [ - "indenter", - "once_cell", -] - -[[package]] -name = "gimli" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" - -[[package]] -name = "heck" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "indenter" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" - -[[package]] -name = "inquire" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6055ce38cac9b10ac819ed4a509d92ccbc60808152c19ff9121c98198964272" -dependencies = [ - "bitflags", - "crossterm", - "dyn-clone", - "lazy_static", - "newline-converter", - "thiserror", - "unicode-segmentation", - "unicode-width", -] - -[[package]] -name = "interactive-clap" -version = "0.1.0" -dependencies = [ - "clap", - "color-eyre", - "inquire", - "interactive-clap-derive", - "shell-words", - "strum", - "strum_macros", -] - -[[package]] -name = "interactive-clap-derive" -version = "0.1.0" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.138" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" - -[[package]] -name = "lock_api" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "memchr" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" - -[[package]] -name = "miniz_oxide" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" -dependencies = [ - "adler", - "autocfg", -] - -[[package]] -name = "mio" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" -dependencies = [ - "libc", - "log", - "wasi", - "windows-sys", -] - -[[package]] -name = "newline-converter" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f71d09d5c87634207f894c6b31b6a2b2c64ea3bdcf71bd5599fdbbe1600c00f" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "object" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" - -[[package]] -name = "os_str_bytes" -version = "6.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" - -[[package]] -name = "owo-colors" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2386b4ebe91c2f7f51082d4cefa145d030e33a1842a96b12e4885cc3c01f7a55" - -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-sys", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro2" -version = "1.0.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "redox_syscall" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" -dependencies = [ - "bitflags", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" - -[[package]] -name = "rustversion" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "sharded-slab" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shell-words" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" - -[[package]] -name = "signal-hook" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-mio" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" -dependencies = [ - "libc", - "mio", - "signal-hook", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" -dependencies = [ - "libc", -] - -[[package]] -name = "smallvec" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" - -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "strum" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.24.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn", -] - -[[package]] -name = "syn" -version = "1.0.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "termcolor" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "thiserror" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thread_local" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" -dependencies = [ - "once_cell", -] - -[[package]] -name = "tracing" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d8d93354fe2a8e50d5953f5ae2e47a3fc2ef03292e7ea46e3cc38f549525fb9" -dependencies = [ - "cfg-if", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" -dependencies = [ - "lazy_static", - "valuable", -] - -[[package]] -name = "tracing-error" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4d7c0b83d4a500748fa5879461652b361edf5c9d51ede2a2ac03875ca185e24" -dependencies = [ - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "tracing-subscriber" -version = "0.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" -dependencies = [ - "sharded-slab", - "thread_local", - "tracing-core", -] - -[[package]] -name = "unicode-ident" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" - -[[package]] -name = "unicode-segmentation" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" - -[[package]] -name = "unicode-width" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" - -[[package]] -name = "unicode-xid" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" - -[[package]] -name = "valuable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[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-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[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-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -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.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" diff --git a/Cargo.toml b/Cargo.toml index f8582e0..2d87000 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ members = [ [package] name = "interactive-clap" -version = "0.1.0" +version = "0.2.0" authors = ["FroVolod "] edition = "2018" license = "MIT OR Apache-2.0" @@ -15,7 +15,7 @@ description = "Interactive mode extension crate to Command Line Arguments Parser # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -interactive-clap-derive = { path = "interactive-clap-derive", version = "0.1.0" } +interactive-clap-derive = { path = "interactive-clap-derive", version = "0.2.0" } strum = { version = "0.24", features = ["derive"] } strum_macros = "0.24" diff --git a/examples/advanced_enum.rs b/examples/advanced_enum.rs new file mode 100644 index 0000000..5ca608c --- /dev/null +++ b/examples/advanced_enum.rs @@ -0,0 +1,71 @@ +//This example shows how to parse data from the command line to an enum using the "interactive-clap" macro. + +// 1) build an example: cargo build --example advanced_enum +// 2) go to the `examples` folder: cd target/debug/examples +// 3) run an example: ./advanced_enum (without parameters) => entered interactive mode +// ./advanced_enum network => mode: Ok(Network) +// ./advanced_enum offline => mode: Ok(Offline) +// To learn more about the parameters, use "help" flag: ./advanced_enum --help + +use interactive_clap::{ResultFromCli, ToCliArgs}; +use strum::{EnumDiscriminants, EnumIter, EnumMessage}; + +#[derive(Debug, EnumDiscriminants, Clone, interactive_clap::InteractiveClap)] +#[strum_discriminants(derive(EnumMessage, EnumIter))] +///To construct a transaction you will need to provide information about sender (signer) and receiver accounts, and actions that needs to be performed. +///Do you want to derive some information required for transaction construction automatically querying it online? +pub enum Mode { + /// Prepare and, optionally, submit a new transaction with online mode + #[strum_discriminants(strum(message = "Yes, I keep it simple"))] + Network(Args), + /// Prepare and, optionally, submit a new transaction with offline mode + #[strum_discriminants(strum( + message = "No, I want to work in no-network (air-gapped) environment" + ))] + Offline, +} + +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] +pub struct Args { + age: u64, + first_name: String, + second_name: String, +} + +fn main() -> color_eyre::Result<()> { + let cli_mode = Mode::try_parse().ok(); + let context = (); // default: input_context = () + let mode = loop { + let mode = ::from_cli(cli_mode.clone(), context); + match mode { + ResultFromCli::Ok(cli_mode) => break cli_mode, + ResultFromCli::Cancel(Some(cli_mode)) => { + println!( + "Your console command: {}", + shell_words::join(&cli_mode.to_cli_args()) + ); + return Ok(()); + } + ResultFromCli::Cancel(None) => { + println!("Goodbye!"); + return Ok(()); + } + ResultFromCli::Back => {} + ResultFromCli::Err(optional_cli_mode, err) => { + if let Some(cli_mode) = optional_cli_mode { + println!( + "Your console command: {}", + shell_words::join(&cli_mode.to_cli_args()) + ); + } + return Err(err); + } + } + }; + println!("cli_mode: {:?}", mode); + println!( + "Your console command: {}", + shell_words::join(&mode.to_cli_args()) + ); + Ok(()) +} diff --git a/examples/advanced_struct.rs b/examples/advanced_struct.rs index 01e6574..d7c427d 100644 --- a/examples/advanced_struct.rs +++ b/examples/advanced_struct.rs @@ -7,15 +7,14 @@ // => args: Ok(Args { age: 30, first_name: "QWE", second_name: "QWERTY" }) // To learn more about the parameters, use "help" flag: ./advanced_struct --help -use inquire::{CustomType, Text}; +use interactive_clap::{ResultFromCli, ToCliArgs}; -#[derive(Debug, interactive_clap_derive::InteractiveClap)] +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] #[interactive_clap(skip_default_from_cli)] struct Args { - #[interactive_clap(long = "age-full-years")] // hgfashdgfajdfsadajsdfh - #[interactive_clap(skip_default_from_cli_arg)] // указать для чего этот атрибут нужен + #[interactive_clap(long = "age-full-years")] #[interactive_clap(skip_default_input_arg)] - age: u64, + age: Option, #[interactive_clap(long)] ///What is your first name? first_name: String, @@ -24,62 +23,97 @@ struct Args { second_name: String, } +impl interactive_clap::FromCli for Args { + type FromCliContext = (); + type FromCliError = color_eyre::eyre::Error; + fn from_cli( + optional_clap_variant: Option<::CliVariant>, + context: Self::FromCliContext, + ) -> ResultFromCli<::CliVariant, Self::FromCliError> + where + Self: Sized + interactive_clap::ToCli, + { + let mut clap_variant = optional_clap_variant.unwrap_or_default(); + if clap_variant.age.is_none() { + clap_variant.age = match Self::input_age(&context) { + Ok(optional_age) => optional_age, + Err(err) => return ResultFromCli::Err(Some(clap_variant), err), + }; + } + let age = clap_variant.age; + if clap_variant.first_name.is_none() { + clap_variant.first_name = match Self::input_first_name(&context) { + Ok(Some(first_name)) => Some(first_name), + Ok(None) => return ResultFromCli::Cancel(Some(clap_variant)), + Err(err) => return ResultFromCli::Err(Some(clap_variant), err), + }; + } + let first_name = clap_variant.first_name.clone().expect("Unexpected error"); + if clap_variant.second_name.is_none() { + clap_variant.second_name = match Self::input_second_name(&context) { + Ok(Some(second_name)) => Some(second_name), + Ok(None) => return ResultFromCli::Cancel(Some(clap_variant)), + Err(err) => return ResultFromCli::Err(Some(clap_variant), err), + }; + } + let second_name = clap_variant.second_name.clone().expect("Unexpected error"); + ResultFromCli::Ok(clap_variant) + } +} + impl Args { - pub fn from_cli( - optional_clap_variant: Option, - context: (), - ) -> color_eyre::eyre::Result> { - let age = Self::from_cli_age( - optional_clap_variant - .clone() - .and_then(|clap_variant| clap_variant.age), - &context, - )?; - let first_name = Self::from_cli_first_name( - optional_clap_variant - .clone() - .and_then(|clap_variant| clap_variant.first_name), - &context, - )?; - let second_name = Self::from_cli_second_name( - optional_clap_variant - .clone() - .and_then(|clap_variant| clap_variant.second_name), - &context, - )?; - let new_context_scope = InteractiveClapContextScopeForArgs { - age, - first_name, - second_name, - }; - Ok(Some(Self { - age: new_context_scope.age, - first_name: new_context_scope.first_name, - second_name: new_context_scope.second_name, - })) + fn input_age(_context: &()) -> color_eyre::eyre::Result> { + match inquire::CustomType::new("Input age full years".to_string().as_str()).prompt() { + Ok(value) => Ok(Some(value)), + Err( + inquire::error::InquireError::OperationCanceled + | inquire::error::InquireError::OperationInterrupted, + ) => Ok(None), + Err(err) => Err(err.into()), + } } - fn input_age(_context: &()) -> color_eyre::eyre::Result { - Ok(CustomType::new("How old are you?").prompt()?) - } - - fn input_second_name(_context: &()) -> color_eyre::eyre::Result { - Ok(Text::new("What is your last name?").prompt()?) - } - - fn from_cli_age( - optional_cli_age: Option, - context: &(), // default: input_context = () - ) -> color_eyre::eyre::Result { - match optional_cli_age { - Some(age) => Ok(age), - None => Self::input_age(&context), + fn input_second_name(_context: &()) -> color_eyre::eyre::Result> { + match inquire::Text::new("Input second name".to_string().as_str()).prompt() { + Ok(value) => Ok(Some(value)), + Err( + inquire::error::InquireError::OperationCanceled + | inquire::error::InquireError::OperationInterrupted, + ) => Ok(None), + Err(err) => Err(err.into()), } } } -fn main() { - let cli_args = Args::parse(); - let args = Args::from_cli(Some(cli_args), ()); - println!("args: {:?}", args) +fn main() -> color_eyre::Result<()> { + let mut cli_args = Args::parse(); + let context = (); // default: input_context = () + loop { + let args = ::from_cli(Some(cli_args), context); + match args { + ResultFromCli::Ok(cli_args) | ResultFromCli::Cancel(Some(cli_args)) => { + println!( + "Your console command: {}", + shell_words::join(&cli_args.to_cli_args()) + ); + return Ok(()); + } + ResultFromCli::Cancel(None) => { + println!("Goodbye!"); + return Ok(()); + } + ResultFromCli::Back => { + cli_args = Default::default(); + } + ResultFromCli::Err(cli_args, err) => { + if let Some(cli_args) = cli_args { + println!( + "Your console command: {}", + shell_words::join(&cli_args.to_cli_args()) + ); + } + return Err(err); + } + } + } } diff --git a/examples/simple_enum.rs b/examples/simple_enum.rs index f3adccf..f9b6c9c 100644 --- a/examples/simple_enum.rs +++ b/examples/simple_enum.rs @@ -7,9 +7,10 @@ // ./simple_enum offline => mode: Ok(Offline) // To learn more about the parameters, use "help" flag: ./simple_enum --help +use interactive_clap::{ResultFromCli, ToCliArgs}; use strum::{EnumDiscriminants, EnumIter, EnumMessage}; -#[derive(Debug, Clone, EnumDiscriminants, interactive_clap_derive::InteractiveClap)] +#[derive(Debug, EnumDiscriminants, Clone, interactive_clap::InteractiveClap)] #[strum_discriminants(derive(EnumMessage, EnumIter))] ///To construct a transaction you will need to provide information about sender (signer) and receiver accounts, and actions that needs to be performed. ///Do you want to derive some information required for transaction construction automatically querying it online? @@ -24,9 +25,33 @@ pub enum Mode { Offline, } -fn main() { +fn main() -> color_eyre::Result<()> { let cli_mode = Mode::try_parse().ok(); let context = (); // default: input_context = () - let mode = ::from_cli(cli_mode, context); - println!("mode: {:?}", mode) + loop { + let mode = ::from_cli(cli_mode.clone(), context); + match mode { + ResultFromCli::Ok(cli_mode) | ResultFromCli::Cancel(Some(cli_mode)) => { + println!( + "Your console command: {}", + shell_words::join(&cli_mode.to_cli_args()) + ); + return Ok(()); + } + ResultFromCli::Cancel(None) => { + println!("Goodbye!"); + return Ok(()); + } + ResultFromCli::Back => {} + ResultFromCli::Err(optional_cli_mode, err) => { + if let Some(cli_mode) = optional_cli_mode { + println!( + "Your console command: {}", + shell_words::join(&cli_mode.to_cli_args()) + ); + } + return Err(err); + } + } + } } diff --git a/examples/simple_struct.rs b/examples/simple_struct.rs index 0d01f46..56ae9b2 100644 --- a/examples/simple_struct.rs +++ b/examples/simple_struct.rs @@ -6,16 +6,44 @@ // ./simple_struct 30 QWE QWERTY => args: Ok(Args { age: 30, first_name: "QWE", second_name: "QWERTY" }) // To learn more about the parameters, use "help" flag: ./simple_struct --help -#[derive(Debug, interactive_clap::InteractiveClap)] -struct Args { +use interactive_clap::{ResultFromCli, ToCliArgs}; + +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] +pub struct Args { age: u64, first_name: String, second_name: String, } -fn main() { - let cli_args = Args::parse(); +fn main() -> color_eyre::Result<()> { + let mut cli_args = Args::parse(); let context = (); // default: input_context = () - let args = ::from_cli(Some(cli_args), context); - println!("args: {:?}", args) + loop { + let args = ::from_cli(Some(cli_args), context); + match args { + ResultFromCli::Ok(cli_args) | ResultFromCli::Cancel(Some(cli_args)) => { + println!( + "Your console command: {}", + shell_words::join(&cli_args.to_cli_args()) + ); + return Ok(()); + } + ResultFromCli::Cancel(None) => { + println!("Goodbye!"); + return Ok(()); + } + ResultFromCli::Back => { + cli_args = Default::default(); + } + ResultFromCli::Err(cli_args, err) => { + if let Some(cli_args) = cli_args { + println!( + "Your console command: {}", + shell_words::join(&cli_args.to_cli_args()) + ); + } + return Err(err); + } + } + } } diff --git a/examples/struct_with_context.rs b/examples/struct_with_context.rs index 0bbd9c5..3ffd053 100644 --- a/examples/struct_with_context.rs +++ b/examples/struct_with_context.rs @@ -6,17 +6,18 @@ // ./struct_with_context account QWERTY => offline_args: Ok(OfflineArgs { account: Sender { sender_account_id: "QWERTY" } }) // To learn more about the parameters, use "help" flag: ./struct_with_context --help -use inquire::Text; +use interactive_clap::{ResultFromCli, ToCliArgs}; mod common; +mod simple_enum; -#[derive(Debug, Clone, interactive_clap_derive::InteractiveClap)] +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] #[interactive_clap(input_context = ())] -#[interactive_clap(output_context = NetworkContext)] +#[interactive_clap(output_context = OfflineArgsContext)] pub struct OfflineArgs { #[interactive_clap(named_arg)] ///Specify a sender - account: Sender, + sender: Sender, } #[derive(Debug)] @@ -27,7 +28,7 @@ pub struct OfflineArgsContext { impl OfflineArgsContext { fn from_previous_context( _previous_context: (), - scope: &::InteractiveClapContextScope, + _scope: &::InteractiveClapContextScope, ) -> color_eyre::eyre::Result { Ok(Self { some_context_field: 42, @@ -43,29 +44,82 @@ impl From for NetworkContext { } } +impl From<()> for NetworkContext { + fn from(_: ()) -> Self { + Self { + connection_config: None, + } + } +} + +impl From for () { + fn from(_: NetworkContext) -> Self { + () + } +} + #[derive(Debug)] pub struct NetworkContext { pub connection_config: Option, } -#[derive(Debug, Clone, interactive_clap_derive::InteractiveClap)] +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] #[interactive_clap(context = NetworkContext)] pub struct Sender { #[interactive_clap(skip_default_input_arg)] - pub sender_account_id: String, + sender_account_id: String, + #[interactive_clap(subcommand)] + network: simple_enum::Mode, } impl Sender { - fn input_sender_account_id(context: &NetworkContext) -> color_eyre::eyre::Result { + fn input_sender_account_id( + context: &NetworkContext, + ) -> color_eyre::eyre::Result> { println!("Let's use context: {:?}", context); - Ok(Text::new("What is the account ID?").prompt()?) + match inquire::CustomType::new("What is the account ID?").prompt() { + Ok(value) => Ok(Some(value)), + Err( + inquire::error::InquireError::OperationCanceled + | inquire::error::InquireError::OperationInterrupted, + ) => Ok(None), + Err(err) => Err(err.into()), + } } } -fn main() { - let cli_offline_args = OfflineArgs::parse(); +fn main() -> color_eyre::Result<()> { + let mut cli_offline_args = OfflineArgs::parse(); let context = (); // #[interactive_clap(input_context = ())] - let offline_args = - ::from_cli(Some(cli_offline_args), context); - println!("offline_args: {:?}", offline_args) + loop { + let offline_args = ::from_cli( + Some(cli_offline_args.clone()), + context, + ); + match offline_args { + ResultFromCli::Ok(cli_offline_args) | ResultFromCli::Cancel(Some(cli_offline_args)) => { + println!( + "Your console command: {}", + shell_words::join(&cli_offline_args.to_cli_args()) + ); + return Ok(()); + } + ResultFromCli::Cancel(None) => { + println!("Goodbye!"); + return Ok(()); + } + ResultFromCli::Back => { + cli_offline_args = Default::default(); + } + ResultFromCli::Err(cli_offline_args, err) => { + if let Some(cli_offline_args) = cli_offline_args { + println!( + "Your console command: {}", + shell_words::join(&cli_offline_args.to_cli_args()) + ); + } + return Err(err); + } + } + } } diff --git a/examples/struct_with_named_arg.rs b/examples/struct_with_named_arg.rs index c387d5b..c2e62b9 100644 --- a/examples/struct_with_named_arg.rs +++ b/examples/struct_with_named_arg.rs @@ -7,22 +7,50 @@ // ./struct_with_named_arg account QWERTY => account: Ok(Account { account: Sender { sender_account_id: "QWERTY" } }) // To learn more about the parameters, use "help" flag: ./struct_with_named_arg --help -#[derive(Debug, Clone, interactive_clap_derive::InteractiveClap)] +use interactive_clap::{ResultFromCli, ToCliArgs}; + +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] struct Account { #[interactive_clap(named_arg)] ///Specify a sender account: Sender, } -#[derive(Debug, Clone, interactive_clap_derive::InteractiveClap)] +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] pub struct Sender { - ///What is the account ID? + ///What is the sender account ID? pub sender_account_id: String, } -fn main() { - let cli_account = Account::parse(); +fn main() -> color_eyre::Result<()> { + let mut cli_account = Account::parse(); let context = (); // default: input_context = () - let account = ::from_cli(Some(cli_account), context); - println!("account: {:?}", account) + loop { + let account = ::from_cli(Some(cli_account), context); + match account { + ResultFromCli::Ok(cli_account) | ResultFromCli::Cancel(Some(cli_account)) => { + println!( + "Your console command: {}", + shell_words::join(&cli_account.to_cli_args()) + ); + return Ok(()); + } + ResultFromCli::Cancel(None) => { + println!("Goodbye!"); + return Ok(()); + } + ResultFromCli::Back => { + cli_account = Default::default(); + } + ResultFromCli::Err(cli_account, err) => { + if let Some(cli_account) = cli_account { + println!( + "Your console command: {}", + shell_words::join(&cli_account.to_cli_args()) + ); + } + return Err(err); + } + } + } } diff --git a/examples/struct_with_subcommand.rs b/examples/struct_with_subcommand.rs index 9b4c6b1..86b96cd 100644 --- a/examples/struct_with_subcommand.rs +++ b/examples/struct_with_subcommand.rs @@ -7,18 +7,49 @@ // ./struct_with_subcommand offline => operation_mode: Ok(OperationMode { mode: Offline }) // To learn more about the parameters, use "help" flag: ./struct_with_subcommand --help +use interactive_clap::{ResultFromCli, ToCliArgs}; + mod simple_enum; -#[derive(Debug, Clone, interactive_clap_derive::InteractiveClap)] +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] pub struct OperationMode { #[interactive_clap(subcommand)] pub mode: simple_enum::Mode, } -fn main() { - let cli_operation_mode = OperationMode::parse(); +fn main() -> color_eyre::Result<()> { + let mut cli_operation_mode = OperationMode::parse(); let context = (); // default: input_context = () - let operation_mode = - ::from_cli(Some(cli_operation_mode), context); - println!("operation_mode: {:?}", &operation_mode); + loop { + let operation_mode = ::from_cli( + Some(cli_operation_mode), + context, + ); + match operation_mode { + ResultFromCli::Ok(cli_operation_mode) + | ResultFromCli::Cancel(Some(cli_operation_mode)) => { + println!( + "Your console command: {}", + shell_words::join(&cli_operation_mode.to_cli_args()) + ); + return Ok(()); + } + ResultFromCli::Cancel(None) => { + println!("Goodbye!"); + return Ok(()); + } + ResultFromCli::Back => { + cli_operation_mode = Default::default(); + } + ResultFromCli::Err(cli_operation_mode, err) => { + if let Some(cli_operation_mode) = cli_operation_mode { + println!( + "Your console command: {}", + shell_words::join(&cli_operation_mode.to_cli_args()) + ); + } + return Err(err); + } + } + } } diff --git a/examples/to_cli_args.rs b/examples/to_cli_args.rs index c1e06b3..4387756 100644 --- a/examples/to_cli_args.rs +++ b/examples/to_cli_args.rs @@ -8,54 +8,105 @@ // To learn more about the parameters, use "help" flag: ./to_cli_args --help use inquire::Select; +use interactive_clap::{ResultFromCli, SelectVariantOrBack, ToCliArgs}; use strum::{EnumDiscriminants, EnumIter, EnumMessage, IntoEnumIterator}; -use interactive_clap::{SelectVariantOrBack, ToCliArgs}; - mod common; -#[derive(Debug, Clone, interactive_clap_derive::InteractiveClap)] +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] #[interactive_clap(context = common::ConnectionConfig)] struct OnlineArgs { + /// What is the name of the network + #[interactive_clap(skip_default_input_arg)] + network_name: String, #[interactive_clap(subcommand)] submit: Submit, } -#[derive(Debug, EnumDiscriminants, Clone, clap::Parser, interactive_clap_derive::ToCliArgs)] +impl OnlineArgs { + fn input_network_name( + _context: &common::ConnectionConfig, + ) -> color_eyre::eyre::Result> { + match inquire::Text::new("Input network name").prompt() { + Ok(value) => Ok(Some(value)), + Err( + inquire::error::InquireError::OperationCanceled + | inquire::error::InquireError::OperationInterrupted, + ) => Ok(None), + Err(err) => Err(err.into()), + } + } +} + +#[derive(Debug, EnumDiscriminants, Clone, clap::Parser)] #[strum_discriminants(derive(EnumMessage, EnumIter))] pub enum Submit { #[strum_discriminants(strum(message = "I want to send the transaction to the network"))] - Send, + Send(Args), #[strum_discriminants(strum( message = "I only want to print base64-encoded transaction for JSON RPC input and exit" ))] Display, } +#[derive(Debug, EnumDiscriminants, Clone, clap::Parser)] +pub enum CliSubmit { + Send(CliArgs), + Display, +} + +impl From for CliSubmit { + fn from(command: Submit) -> Self { + match command { + Submit::Send(args) => Self::Send(args.into()), + Submit::Display => Self::Display, + } + } +} + impl interactive_clap::FromCli for Submit { type FromCliContext = common::ConnectionConfig; type FromCliError = color_eyre::eyre::Error; fn from_cli( optional_clap_variant: Option<::CliVariant>, - _context: Self::FromCliContext, - ) -> Result, Self::FromCliError> + context: Self::FromCliContext, + ) -> ResultFromCli<::CliVariant, Self::FromCliError> where Self: Sized + interactive_clap::ToCli, { - let submit: Option = optional_clap_variant.clone(); - match submit { - Some(submit) => Ok(Some(submit)), - None => Ok(Some(Submit::Display)), + match optional_clap_variant { + Some(submit) => ResultFromCli::Ok(submit), + None => Self::choose_variant(context), + } + } +} + +impl interactive_clap::ToCliArgs for CliSubmit { + fn to_cli_args(&self) -> std::collections::VecDeque { + match self { + Self::Send(cli_args) => { + let mut args = cli_args.to_cli_args(); + args.push_front("send".to_owned()); + args + } + Self::Display => { + let mut args = std::collections::VecDeque::new(); + args.push_front("display".to_owned()); + args + } } } } impl Submit { fn choose_variant( - _context: common::ConnectionConfig, - ) -> color_eyre::eyre::Result> { - let selected_variant = Select::new( + context: common::ConnectionConfig, + ) -> ResultFromCli< + ::CliVariant, + ::FromCliError, + > { + match Select::new( "How would you like to proceed", SubmitDiscriminants::iter() .map(SelectVariantOrBack::Variant) @@ -63,17 +114,40 @@ impl Submit { .collect(), ) .prompt() - .unwrap(); - match selected_variant { - SelectVariantOrBack::Variant(SubmitDiscriminants::Send) => Ok(Some(Submit::Send)), - SelectVariantOrBack::Variant(SubmitDiscriminants::Display) => Ok(Some(Submit::Display)), - SelectVariantOrBack::Back => Ok(None), + { + Ok(SelectVariantOrBack::Variant(variant)) => ResultFromCli::Ok(match variant { + SubmitDiscriminants::Send => { + let cli_args = + match ::from_cli(None, context) { + ResultFromCli::Ok(cli_args) => cli_args, + ResultFromCli::Cancel(optional_cli_args) => { + return ResultFromCli::Cancel(Some(CliSubmit::Send( + optional_cli_args.unwrap_or_default(), + ))); + } + ResultFromCli::Back => return ResultFromCli::Back, + ResultFromCli::Err(optional_cli_args, err) => { + return ResultFromCli::Err( + Some(CliSubmit::Send(optional_cli_args.unwrap_or_default())), + err, + ); + } + }; + CliSubmit::Send(cli_args) + } + SubmitDiscriminants::Display => CliSubmit::Display, + }), + Ok(SelectVariantOrBack::Back) => ResultFromCli::Back, + Err( + inquire::error::InquireError::OperationCanceled + | inquire::error::InquireError::OperationInterrupted, + ) => ResultFromCli::Cancel(None), + Err(err) => ResultFromCli::Err(None, err.into()), } } } - impl interactive_clap::ToCli for Submit { - type CliVariant = Submit; + type CliVariant = CliSubmit; } impl std::fmt::Display for SubmitDiscriminants { @@ -85,23 +159,52 @@ impl std::fmt::Display for SubmitDiscriminants { } } -fn main() { +#[derive(Debug, Clone, interactive_clap::InteractiveClap, clap::Args)] +#[interactive_clap(context = common::ConnectionConfig)] +pub struct Args { + age: u64, + first_name: String, + second_name: String, +} + +fn main() -> color_eyre::Result<()> { let mut cli_online_args = OnlineArgs::parse(); let context = common::ConnectionConfig::Testnet; //#[interactive_clap(context = common::ConnectionConfig)] - let online_args = loop { - if let Some(args) = ::from_cli( - Some(cli_online_args.clone()), + let cli_args = loop { + match ::from_cli( + Some(cli_online_args), context.clone(), - ) - .unwrap() - { - break args; + ) { + ResultFromCli::Ok(cli_args) => break cli_args, + ResultFromCli::Cancel(Some(cli_args)) => { + println!( + "Your console command: {}", + shell_words::join(&cli_args.to_cli_args()) + ); + return Ok(()); + } + ResultFromCli::Cancel(None) => { + println!("Goodbye!"); + return Ok(()); + } + ResultFromCli::Back => { + cli_online_args = Default::default(); + } + ResultFromCli::Err(cli_args, err) => { + if let Some(cli_args) = cli_args { + println!( + "Your console command: {}", + shell_words::join(&cli_args.to_cli_args()) + ); + } + return Err(err); + } } }; - cli_online_args = online_args.into(); - let completed_cli = cli_online_args.to_cli_args(); + println!("cli_args: {:?}", cli_args); println!( "Your console command: {}", - shell_words::join(&completed_cli) + shell_words::join(&cli_args.to_cli_args()) ); + Ok(()) } diff --git a/interactive-clap-derive/Cargo.toml b/interactive-clap-derive/Cargo.toml index bd093e6..1d7c190 100644 --- a/interactive-clap-derive/Cargo.toml +++ b/interactive-clap-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "interactive-clap-derive" -version = "0.1.0" +version = "0.2.0" authors = ["FroVolod "] edition = "2018" license = "MIT OR Apache-2.0" diff --git a/interactive-clap-derive/src/derives/interactive_clap/methods/choose_variant.rs b/interactive-clap-derive/src/derives/interactive_clap/methods/choose_variant.rs index 09cec3d..c628911 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/methods/choose_variant.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/methods/choose_variant.rs @@ -12,13 +12,10 @@ pub fn fn_choose_variant( let name = &ast.ident; let interactive_clap_attrs_context = super::interactive_clap_attrs_context::InteractiveClapAttrsContext::new(ast); - let command_discriminants = - syn::Ident::new(&format!("{}Discriminants", name), Span::call_site()); - let cli_command = syn::Ident::new(&format!("Cli{}", name), Span::call_site()); + let command_discriminants = syn::Ident::new(&format!("{name}Discriminants"), Span::call_site()); + let cli_command = syn::Ident::new(&format!("Cli{name}"), Span::call_site()); - let variant_ident = &variants[0].ident; let mut cli_variant = quote!(); - let mut actions_push_back = quote! {.chain([SelectVariantOrBack::Back])}; let mut ast_attrs: Vec<&str> = std::vec::Vec::new(); if !ast.attrs.is_empty() { @@ -26,13 +23,7 @@ pub fn fn_choose_variant( if attr.path.is_ident("interactive_clap") { for attr_token in attr.tokens.clone() { if let proc_macro2::TokenTree::Group(group) = attr_token { - if group - .stream() - .to_string() - .contains("disable_strum_discriminants") - { - ast_attrs.push("disable_strum_discriminants"); - } else if group.stream().to_string().contains("disable_back") { + if group.stream().to_string().contains("disable_back") { ast_attrs.push("disable_back"); }; } @@ -48,106 +39,91 @@ pub fn fn_choose_variant( } }; } - if ast_attrs.contains(&"disable_strum_discriminants") { - match &variants[0].fields { - syn::Fields::Unnamed(_) => { - cli_variant = quote! { - let cli_variant = #cli_command::#variant_ident(Default::default()); - }; - } - syn::Fields::Unit => { - cli_variant = quote! { - let cli_variant = #cli_command::#variant_ident; - }; - } - _ => abort_call_site!("Only option `Fields::Unnamed` or `Fields::Unit` is needed"), - } - } else { - if ast_attrs.contains(&"disable_back") { - actions_push_back = quote!(); - } - if ast_attrs.contains(&"strum_discriminants") { - let doc_attrs = ast - .attrs - .iter() - .filter(|attr| attr.path.is_ident("doc")) - .map(|attr| { - let mut literal_string = String::new(); - for attr_token in attr.tokens.clone() { - if let proc_macro2::TokenTree::Literal(literal) = attr_token { - literal_string = literal.to_string(); - } + if ast_attrs.contains(&"strum_discriminants") { + let doc_attrs = ast + .attrs + .iter() + .filter(|attr| attr.path.is_ident("doc")) + .map(|attr| { + let mut literal_string = String::new(); + for attr_token in attr.tokens.clone() { + if let proc_macro2::TokenTree::Literal(literal) = attr_token { + literal_string = literal.to_string(); } - literal_string - }) - .collect::>(); - let literal_vec = doc_attrs - .iter() - .map(|s| s.replace('\"', "")) - .collect::>(); - let literal = proc_macro2::Literal::string(literal_vec.join("\n ").as_str()); - - let enum_variants = variants.iter().map(|variant| { - let variant_ident = &variant.ident; - - match &variant.fields { - syn::Fields::Unnamed(_) => { - quote! { - #command_discriminants::#variant_ident => #cli_command::#variant_ident(Default::default()) - } - }, - syn::Fields::Unit => { - quote! { - #command_discriminants::#variant_ident => #cli_command::#variant_ident - } - }, - _ => abort_call_site!("Only option `Fields::Unnamed` or `Fields::Unit` is needed") } - }); - cli_variant = quote! { - use interactive_clap::SelectVariantOrBack; - use inquire::Select; - use strum::{EnumMessage, IntoEnumIterator}; - fn prompt_variant(prompt: &str) -> color_eyre::eyre::Result> - where - T: IntoEnumIterator + EnumMessage, - T: Copy + Clone, - { - let selected_variant = Select::new( - prompt, - T::iter() - .map(SelectVariantOrBack::Variant) - #actions_push_back - .collect(), - ) - .prompt()?; - match selected_variant { - SelectVariantOrBack::Variant(variant) => Ok(Some(variant)), - SelectVariantOrBack::Back => Ok(None), + literal_string + }) + .collect::>(); + let literal_vec = doc_attrs + .iter() + .map(|s| s.replace('\"', "")) + .collect::>(); + let literal = proc_macro2::Literal::string(literal_vec.join("\n ").as_str()); + + let enum_variants = variants.iter().map(|variant| { + let variant_ident = &variant.ident; + match &variant.fields { + syn::Fields::Unnamed(_) => { + quote! { + #command_discriminants::#variant_ident => { + #cli_command::#variant_ident(Default::default()) + } } - }; - let variant = if let Some(variant) = prompt_variant(#literal.to_string().as_str())? { - variant - } else { - return Ok(None); - }; - let cli_variant = match variant { - #( #enum_variants, )* - }; - }; - } + } + syn::Fields::Unit => { + quote! { + #command_discriminants::#variant_ident => #cli_command::#variant_ident + } + } + _ => abort_call_site!( + "Only option `Fields::Unnamed` or `Fields::Unit` is needed" + ), + } + }); + let actions_push_back = if ast_attrs.contains(&"disable_back") { + quote!() + } else { + quote! {.chain([SelectVariantOrBack::Back])} + }; + + cli_variant = quote! { + use interactive_clap::SelectVariantOrBack; + use inquire::Select; + use strum::{EnumMessage, IntoEnumIterator}; + + let selected_variant = Select::new( + #literal, + #command_discriminants::iter() + .map(SelectVariantOrBack::Variant) + #actions_push_back + .collect(), + ) + .prompt(); + match selected_variant { + Ok(SelectVariantOrBack::Variant(variant)) => { + let cli_args = match variant { + #( #enum_variants, )* + }; + return interactive_clap::ResultFromCli::Ok(cli_args); + }, + Ok(SelectVariantOrBack::Back) => return interactive_clap::ResultFromCli::Back, + Err( + inquire::error::InquireError::OperationCanceled + | inquire::error::InquireError::OperationInterrupted, + ) => return interactive_clap::ResultFromCli::Cancel(None), + Err(err) => return interactive_clap::ResultFromCli::Err(None, err.into()), + } + }; } }; - let input_context = interactive_clap_attrs_context.get_input_context_dir(); + let context = interactive_clap_attrs_context.get_input_context_dir(); quote! { - pub fn choose_variant(context: #input_context) -> color_eyre::eyre::Result> { - loop { - #cli_variant - if let Some(variant) = ::from_cli(Some(cli_variant), context.clone())? { - return Ok(Some(variant)); - } - } - } + pub fn choose_variant(context: #context) -> interactive_clap::ResultFromCli< + ::CliVariant, + ::FromCliError, + > { + #cli_variant + } } } diff --git a/interactive-clap-derive/src/derives/interactive_clap/methods/fields_without_skip_default_from_cli_arg.rs b/interactive-clap-derive/src/derives/interactive_clap/methods/fields_without_skip_default_from_cli_arg.rs deleted file mode 100644 index 0292b7d..0000000 --- a/interactive-clap-derive/src/derives/interactive_clap/methods/fields_without_skip_default_from_cli_arg.rs +++ /dev/null @@ -1,21 +0,0 @@ -extern crate proc_macro; - -use syn; - -pub fn is_field_without_skip_default_from_cli_arg(field: &syn::Field) -> bool { - if field.attrs.is_empty() { - return true; - } - !field - .attrs - .iter() - .filter(|attr| attr.path.is_ident("interactive_clap")) - .flat_map(|attr| attr.tokens.clone()) - .any(|attr_token| match attr_token { - proc_macro2::TokenTree::Group(group) => group - .stream() - .to_string() - .contains("skip_default_from_cli_arg"), - _ => false, // abort_call_site!("Only option `TokenTree::Group` is needed") - }) -} diff --git a/interactive-clap-derive/src/derives/interactive_clap/methods/from_cli_for_enum.rs b/interactive-clap-derive/src/derives/interactive_clap/methods/from_cli_for_enum.rs index eb22e0f..5f1db73 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/methods/from_cli_for_enum.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/methods/from_cli_for_enum.rs @@ -10,7 +10,7 @@ pub fn from_cli_for_enum( variants: &syn::punctuated::Punctuated, ) -> proc_macro2::TokenStream { let name = &ast.ident; - let cli_name = syn::Ident::new(&format!("Cli{}", name), Span::call_site()); + let cli_name = syn::Ident::new(&format!("Cli{name}"), Span::call_site()); let interactive_clap_attrs_context = super::interactive_clap_attrs_context::InteractiveClapAttrsContext::new(ast); @@ -20,42 +20,74 @@ pub fn from_cli_for_enum( let from_cli_variants = variants.iter().map(|variant| { let variant_ident = &variant.ident; + + let output_context = match &interactive_clap_attrs_context.output_context_dir { + Some(output_context_dir) => { + quote! { + type Alias = <#name as interactive_clap::ToInteractiveClapContextScope>::InteractiveClapContextScope; + let new_context_scope = Alias::#variant_ident; + let output_context = match #output_context_dir::from_previous_context(context.clone(), &new_context_scope) { + Ok(new_context) => new_context, + Err(err) => return interactive_clap::ResultFromCli::Err(Some(#cli_name::#variant_ident(inner_cli_args)), err), + }; + } + } + None => { + quote! { + let output_context = context.clone(); + } + } + }; + match &variant.fields { syn::Fields::Unnamed(fields) => { let ty = &fields.unnamed[0].ty; - let context_name = syn::Ident::new(&format!("{}Context", &name), Span::call_site()); - - - match &interactive_clap_attrs_context.output_context_dir { - Some(output_context_dir) => quote! { - Some(#cli_name::#variant_ident(inner_cli_args)) => { - type Alias = <#name as interactive_clap::ToInteractiveClapContextScope>::InteractiveClapContextScope; - let new_context_scope = Alias::#variant_ident; - let new_context = #context_name::from_previous_context(context.clone(), &new_context_scope)?; - let output_context = #output_context_dir::from(new_context); - let optional_inner_args = <#ty as interactive_clap::FromCli>::from_cli(Some(inner_cli_args), output_context)?; - if let Some(inner_args) = optional_inner_args { - Ok(Some(Self::#variant_ident(inner_args,))) - } else { - Self::choose_variant(context.clone()) + quote! { + Some(#cli_name::#variant_ident(inner_cli_args)) => { + #output_context + let cli_inner_args = <#ty as interactive_clap::FromCli>::from_cli(Some(inner_cli_args), output_context.into()); + match cli_inner_args { + interactive_clap::ResultFromCli::Ok(cli_args) => { + interactive_clap::ResultFromCli::Ok(#cli_name::#variant_ident(cli_args)) } - } - }, - None => quote! { - Some(#cli_name::#variant_ident(inner_cli_args)) => { - let optional_inner_args = <#ty as interactive_clap::FromCli>::from_cli(Some(inner_cli_args), context.clone().into())?; - if let Some(inner_args) = optional_inner_args { - Ok(Some(Self::#variant_ident(inner_args,))) - } else { - Self::choose_variant(context.clone()) + interactive_clap::ResultFromCli::Back => { + optional_clap_variant = None; + continue; + }, + interactive_clap::ResultFromCli::Cancel(Some(cli_args)) => { + interactive_clap::ResultFromCli::Cancel(Some(#cli_name::#variant_ident(cli_args))) + } + interactive_clap::ResultFromCli::Cancel(None) => { + interactive_clap::ResultFromCli::Cancel(None) + } + interactive_clap::ResultFromCli::Err(Some(cli_args), err) => { + interactive_clap::ResultFromCli::Err(Some(#cli_name::#variant_ident(cli_args)), err) + } + interactive_clap::ResultFromCli::Err(None, err) => { + interactive_clap::ResultFromCli::Err(None, err) } } } } }, syn::Fields::Unit => { - quote! { - Some(#cli_name::#variant_ident) => Ok(Some(Self::#variant_ident)), + match &interactive_clap_attrs_context.output_context_dir { + Some(output_context_dir) => quote! { + Some(#cli_name::#variant_ident) => { + type Alias = <#name as interactive_clap::ToInteractiveClapContextScope>::InteractiveClapContextScope; + let new_context_scope = Alias::#variant_ident; + let output_context = match #output_context_dir::from_previous_context(context.clone(), &new_context_scope) { + Ok(new_context) => new_context, + Err(err) => return interactive_clap::ResultFromCli::Err(Some(#cli_name::#variant_ident), err), + }; + interactive_clap::ResultFromCli::Ok(#cli_name::#variant_ident) + } + }, + None => quote! { + Some(#cli_name::#variant_ident) => { + interactive_clap::ResultFromCli::Ok(#cli_name::#variant_ident) + }, + } } }, _ => abort_call_site!("Only option `Fields::Unnamed` or `Fields::Unit` is needed") @@ -71,12 +103,20 @@ pub fn from_cli_for_enum( type FromCliContext = #input_context_dir; type FromCliError = color_eyre::eyre::Error; fn from_cli( - optional_clap_variant: Option<::CliVariant>, + mut optional_clap_variant: Option<::CliVariant>, context: Self::FromCliContext, - ) -> Result, Self::FromCliError> where Self: Sized + interactive_clap::ToCli { - match optional_clap_variant { - #(#from_cli_variants)* - None => Self::choose_variant(context.clone()), + ) -> interactive_clap::ResultFromCli<::CliVariant, Self::FromCliError> where Self: Sized + interactive_clap::ToCli { + loop { + return match optional_clap_variant { + #(#from_cli_variants)* + None => match Self::choose_variant(context.clone()) { + interactive_clap::ResultFromCli::Ok(cli_args) => { + optional_clap_variant = Some(cli_args); + continue; + }, + result => return result, + }, + } } } } diff --git a/interactive-clap-derive/src/derives/interactive_clap/methods/from_cli_for_struct.rs b/interactive-clap-derive/src/derives/interactive_clap/methods/from_cli_for_struct.rs index e532bca..1d75873 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/methods/from_cli_for_struct.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/methods/from_cli_for_struct.rs @@ -33,13 +33,7 @@ pub fn from_cli_for_struct( let field_value_named_arg = if let Some(token_stream) = fields .iter() - .map(|field| { - field_value_named_arg( - name, - field, - &interactive_clap_attrs_context.output_context_dir, - ) - }) + .map(|field| field_value_named_arg(name, field)) .find(|token_stream| !token_stream.is_empty()) { token_stream @@ -49,13 +43,7 @@ pub fn from_cli_for_struct( let field_value_subcommand = if let Some(token_stream) = fields .iter() - .map(|field| { - field_value_subcommand( - name, - field, - &interactive_clap_attrs_context.output_context_dir, - ) - }) + .map(field_value_subcommand) .find(|token_stream| !token_stream.is_empty()) { token_stream @@ -63,11 +51,9 @@ pub fn from_cli_for_struct( quote!() }; - let struct_fields = fields - .iter() - .map(|field| struct_field(field, &fields_without_subcommand)); - - let input_context_dir = interactive_clap_attrs_context.get_input_context_dir(); + let input_context_dir = interactive_clap_attrs_context + .clone() + .get_input_context_dir(); let interactive_clap_context_scope_for_struct = syn::Ident::new( &format!("InteractiveClapContextScopeFor{}", &name), @@ -77,6 +63,19 @@ pub fn from_cli_for_struct( let new_context_scope = #interactive_clap_context_scope_for_struct { #(#fields_without_subcommand,)* }; }; + let output_context = match &interactive_clap_attrs_context.output_context_dir { + Some(output_context_dir) => { + quote! { + let output_context = match #output_context_dir::from_previous_context(context.clone(), &new_context_scope) { + Ok(new_context) => new_context, + Err(err) => return interactive_clap::ResultFromCli::Err(Some(clap_variant), err), + }; + let context = output_context; + } + } + None => quote!(), + }; + quote! { impl interactive_clap::FromCli for #name { type FromCliContext = #input_context_dir; @@ -84,12 +83,14 @@ pub fn from_cli_for_struct( fn from_cli( optional_clap_variant: Option<::CliVariant>, context: Self::FromCliContext, - ) -> Result, Self::FromCliError> where Self: Sized + interactive_clap::ToCli { + ) -> interactive_clap::ResultFromCli<::CliVariant, Self::FromCliError> where Self: Sized + interactive_clap::ToCli { + let mut clap_variant = optional_clap_variant.unwrap_or_default(); #(#fields_value)* #new_context_scope + #output_context #field_value_named_arg #field_value_subcommand; - Ok(Some(Self{ #(#struct_fields,)* })) + interactive_clap::ResultFromCli::Ok(clap_variant) } } } @@ -97,26 +98,25 @@ pub fn from_cli_for_struct( fn fields_value(field: &syn::Field) -> proc_macro2::TokenStream { let ident_field = &field.clone().ident.expect("this field does not exist"); - let fn_from_cli_arg = syn::Ident::new(&format!("from_cli_{}", &ident_field), Span::call_site()); + let fn_input_arg = syn::Ident::new(&format!("input_{}", &ident_field), Span::call_site()); if super::fields_without_subcommand::is_field_without_subcommand(field) { quote! { - let #ident_field = Self::#fn_from_cli_arg( - optional_clap_variant - .clone() - .and_then(|clap_variant| clap_variant.#ident_field), - &context, - )?; + if clap_variant.#ident_field.is_none() { + clap_variant + .#ident_field = match Self::#fn_input_arg(&context) { + Ok(Some(#ident_field)) => Some(#ident_field), + Ok(None) => return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)), + Err(err) => return interactive_clap::ResultFromCli::Err(Some(clap_variant), err), + }; + }; + let #ident_field = clap_variant.#ident_field.clone().expect("Unexpected error"); } } else { quote!() } } -fn field_value_named_arg( - name: &syn::Ident, - field: &syn::Field, - output_context_dir: &Option, -) -> proc_macro2::TokenStream { +fn field_value_named_arg(name: &syn::Ident, field: &syn::Field) -> proc_macro2::TokenStream { let ident_field = &field.clone().ident.expect("this field does not exist"); let ty = &field.ty; if field.attrs.is_empty() { @@ -144,38 +144,26 @@ fn field_value_named_arg( let enum_for_clap_named_arg = syn::Ident::new(&format!("ClapNamedArg{}For{}", &type_string, &name), Span::call_site()); let variant_name_string = crate::helpers::snake_case_to_camel_case::snake_case_to_camel_case(ident_field.to_string()); let variant_name = &syn::Ident::new(&variant_name_string, Span::call_site()); - match output_context_dir { - Some(output_context_dir) => { - let context_for_struct = syn::Ident::new(&format!("{}Context", &name), Span::call_site()); - quote! { - let new_context = #context_for_struct::from_previous_context(context, &new_context_scope)?; - let output_context = #output_context_dir::from(new_context); - let #ident_field = <#ty as interactive_clap::FromCli>::from_cli( - optional_clap_variant.and_then(|clap_variant| match clap_variant.#ident_field { - Some(#enum_for_clap_named_arg::#variant_name(cli_arg)) => Some(cli_arg), - None => None, - }), - output_context, - )?; - let #ident_field = if let Some(value) = #ident_field { - value - } else { - return Ok(None); - } + quote! { + let optional_field = match clap_variant.#ident_field.take() { + Some(#enum_for_clap_named_arg::#variant_name(cli_arg)) => Some(cli_arg), + None => None, + }; + match <#ty as interactive_clap::FromCli>::from_cli( + optional_field, + context.into(), + ) { + interactive_clap::ResultFromCli::Ok(cli_field) => { + clap_variant.#ident_field = Some(#enum_for_clap_named_arg::#variant_name(cli_field)); } - }, - None => quote! { - let #ident_field = <#ty as interactive_clap::FromCli>::from_cli( - optional_clap_variant.and_then(|clap_variant| match clap_variant.#ident_field { - Some(#enum_for_clap_named_arg::#variant_name(cli_sender)) => Some(cli_sender), - None => None, - }), - context.into(), - )?; - let #ident_field = if let Some(value) = #ident_field { - value - } else { - return Ok(None); + interactive_clap::ResultFromCli::Cancel(optional_cli_field) => { + clap_variant.#ident_field = optional_cli_field.map(#enum_for_clap_named_arg::#variant_name); + return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)); + } + interactive_clap::ResultFromCli::Back => return interactive_clap::ResultFromCli::Back, + interactive_clap::ResultFromCli::Err(optional_cli_field, err) => { + clap_variant.#ident_field = optional_cli_field.map(#enum_for_clap_named_arg::#variant_name); + return interactive_clap::ResultFromCli::Err(Some(clap_variant), err); } } } @@ -187,11 +175,7 @@ fn field_value_named_arg( } } -fn field_value_subcommand( - name: &syn::Ident, - field: &syn::Field, - output_context_dir: &Option, -) -> proc_macro2::TokenStream { +fn field_value_subcommand(field: &syn::Field) -> proc_macro2::TokenStream { let ident_field = &field.clone().ident.expect("this field does not exist"); let ty = &field.ty; if field.attrs.is_empty() { @@ -207,32 +191,23 @@ fn field_value_subcommand( } }) .map(|_| { - match output_context_dir { - Some(output_context_dir) => { - let context_for_struct = syn::Ident::new(&format!("{}Context", &name), Span::call_site()); - quote! { - let new_context = #context_for_struct::from_previous_context(context, &new_context_scope)?; - let output_context = #output_context_dir::from(new_context); - let #ident_field = match optional_clap_variant.and_then(|clap_variant| clap_variant.#ident_field) { - Some(cli_arg) => <#ty as interactive_clap::FromCli>::from_cli(Some(cli_arg), output_context)?, - None => #ty::choose_variant(output_context)?, - }; - let #ident_field = if let Some(value) = #ident_field { - value - } else { - return Ok(None); - } + quote! { + match <#ty as interactive_clap::FromCli>::from_cli(clap_variant.#ident_field.take(), context.into()) { + interactive_clap::ResultFromCli::Ok(cli_field) => { + clap_variant.#ident_field = Some(cli_field); } - }, - None => quote! { - let #ident_field = match optional_clap_variant.and_then(|clap_variant| clap_variant.#ident_field) { - Some(cli_arg) => <#ty as interactive_clap::FromCli>::from_cli(Some(cli_arg), context)?, - None => #ty::choose_variant(context.into())?, - }; - let #ident_field = if let Some(value) = #ident_field { - value - } else { - return Ok(None); + interactive_clap::ResultFromCli::Cancel(option_cli_field) => { + clap_variant.#ident_field = option_cli_field; + return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)); + } + interactive_clap::ResultFromCli::Cancel(option_cli_field) => { + clap_variant.#ident_field = option_cli_field; + return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)); + } + interactive_clap::ResultFromCli::Back => return interactive_clap::ResultFromCli::Back, + interactive_clap::ResultFromCli::Err(option_cli_field, err) => { + clap_variant.#ident_field = option_cli_field; + return interactive_clap::ResultFromCli::Err(Some(clap_variant), err); } } } @@ -243,23 +218,3 @@ fn field_value_subcommand( } } } - -fn struct_field( - field: &syn::Field, - fields_without_subcommand: &[proc_macro2::TokenStream], -) -> proc_macro2::TokenStream { - let ident_field = &field.clone().ident.expect("this field does not exist"); - if fields_without_subcommand - .iter() - .map(|token_stream| token_stream.to_string()) - .any(|x| *ident_field == x) - { - quote! { - #ident_field: new_context_scope.#ident_field - } - } else { - quote! { - #ident_field - } - } -} diff --git a/interactive-clap-derive/src/derives/interactive_clap/methods/get_arg_from_cli_for_struct.rs b/interactive-clap-derive/src/derives/interactive_clap/methods/get_arg_from_cli_for_struct.rs deleted file mode 100644 index 830399b..0000000 --- a/interactive-clap-derive/src/derives/interactive_clap/methods/get_arg_from_cli_for_struct.rs +++ /dev/null @@ -1,104 +0,0 @@ -extern crate proc_macro; - -use proc_macro2::Span; -use quote::quote; -use syn; - -pub fn from_cli_arg(ast: &syn::DeriveInput, fields: &syn::Fields) -> Vec { - let interactive_clap_attrs_context = - super::interactive_clap_attrs_context::InteractiveClapAttrsContext::new(ast); - - let fields_without_subcommand = fields - .iter() - .filter(|field| super::fields_without_subcommand::is_field_without_subcommand(field)) - .map(|field| { - let ident_field = &field.clone().ident.expect("this field does not exist"); - quote! {#ident_field} - }) - .collect::>(); - - let fields_without_skip_default_from_cli_arg = fields - .iter() - .filter(|field| { - super::fields_without_skip_default_from_cli_arg::is_field_without_skip_default_from_cli_arg( - field, - ) - }) - .map(|field| { - let ident_field = &field.clone().ident.expect("this field does not exist"); - quote! {#ident_field} - }) - .collect::>(); - - let get_arg_for_fields = fields - .iter() - .map(|field| { - let ident_field = &field.clone().ident.expect("this field does not exist"); - let ty = &field.ty; - let fields_without_subcommand_to_string = fields_without_subcommand - .iter() - .map(|token_stream| token_stream.to_string()) - .collect::>(); - let fields_without_skip_default_from_cli_arg_to_string = - fields_without_skip_default_from_cli_arg - .iter() - .map(|token_stream| token_stream.to_string()) - .collect::>(); - if fields_without_subcommand_to_string.contains(&ident_field.to_string()) - & fields_without_skip_default_from_cli_arg_to_string - .iter() - .map(|token_stream| token_stream.to_string()) - .any(|x| *ident_field == x) - { - let fn_from_cli_arg = - syn::Ident::new(&format!("from_cli_{}", &ident_field), Span::call_site()); - let optional_cli_field_name = - syn::Ident::new(&format!("optional_cli_{}", ident_field), Span::call_site()); - let input_context_dir = interactive_clap_attrs_context - .clone() - .get_input_context_dir(); - let cli_field_type = super::cli_field_type::cli_field_type(ty); - let fn_input_arg = - syn::Ident::new(&format!("input_{}", &ident_field), Span::call_site()); - - let type_string = match &ty { - syn::Type::Path(type_path) => match type_path.path.segments.last() { - Some(path_segment) => path_segment.ident.to_string(), - _ => String::new(), - }, - _ => String::new(), - }; - if let "Option" = type_string.as_str() { - quote! { - fn #fn_from_cli_arg( - #optional_cli_field_name: #cli_field_type, - context: &#input_context_dir, - ) -> color_eyre::eyre::Result<#ty> { - match #optional_cli_field_name { - Some(#ident_field) => Ok(Some(#ident_field)), - None => Self::#fn_input_arg(&context), - } - } - } - } else { - quote! { - fn #fn_from_cli_arg( - #optional_cli_field_name: #cli_field_type, - context: &#input_context_dir, - ) -> color_eyre::eyre::Result<#ty> { - match #optional_cli_field_name { - Some(#ident_field) => Ok(#ident_field), - None => Self::#fn_input_arg(&context), - } - } - } - } - } else { - quote!() - } - }) - .filter(|token_stream| !token_stream.is_empty()) - .collect::>(); - - get_arg_for_fields -} diff --git a/interactive-clap-derive/src/derives/interactive_clap/methods/input_arg.rs b/interactive-clap-derive/src/derives/interactive_clap/methods/input_arg.rs index 5800b76..48391ce 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/methods/input_arg.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/methods/input_arg.rs @@ -34,8 +34,12 @@ pub fn vec_fn_input_arg( return quote! { fn #fn_input_arg( _context: &#input_context_dir, - ) -> color_eyre::eyre::Result<#ty> { - Ok(inquire::CustomType::new(#promt).prompt()?) + ) -> color_eyre::eyre::Result> { + match inquire::CustomType::new(#promt).prompt() { + Ok(value) => Ok(Some(value)), + Err(inquire::error::InquireError::OperationCanceled | inquire::error::InquireError::OperationInterrupted) => Ok(None), + Err(err) => Err(err.into()), + } } }; } @@ -63,8 +67,12 @@ pub fn vec_fn_input_arg( quote! { fn #fn_input_arg( _context: &#input_context_dir, - ) -> color_eyre::eyre::Result<#ty> { - Ok(inquire::CustomType::new(#literal.to_string().as_str()).prompt()?) + ) -> color_eyre::eyre::Result> { + match inquire::CustomType::new(#literal).prompt() { + Ok(value) => Ok(Some(value)), + Err(inquire::error::InquireError::OperationCanceled | inquire::error::InquireError::OperationInterrupted) => Ok(None), + Err(err) => Err(err.into()), + } } } }) diff --git a/interactive-clap-derive/src/derives/interactive_clap/methods/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/methods/mod.rs index 429190f..17f11d9 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/methods/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/methods/mod.rs @@ -1,10 +1,8 @@ pub mod choose_variant; pub mod cli_field_type; -pub mod fields_without_skip_default_from_cli_arg; pub mod fields_without_skip_default_input_arg; pub mod fields_without_subcommand; pub mod from_cli_for_enum; pub mod from_cli_for_struct; -pub mod get_arg_from_cli_for_struct; pub mod input_arg; pub mod interactive_clap_attrs_context; diff --git a/interactive-clap-derive/src/derives/interactive_clap/mod.rs b/interactive-clap-derive/src/derives/interactive_clap/mod.rs index 1f2cf6e..ce80728 100644 --- a/interactive-clap-derive/src/derives/interactive_clap/mod.rs +++ b/interactive-clap-derive/src/derives/interactive_clap/mod.rs @@ -112,8 +112,6 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { let fn_from_cli_for_struct = self::methods::from_cli_for_struct::from_cli_for_struct(ast, &fields); - let fn_get_arg = self::methods::get_arg_from_cli_for_struct::from_cli_arg(ast, &fields); - let vec_fn_input_arg = self::methods::input_arg::vec_fn_input_arg(ast, &fields); let context_scope_fields = fields @@ -175,7 +173,7 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { }; let gen = quote! { - #[derive(Debug, Default, Clone, clap::Parser, interactive_clap_derive::ToCliArgs)] + #[derive(Debug, Default, Clone, clap::Parser, interactive_clap::ToCliArgs)] #[clap(author, version, about, long_about = None)] pub struct #cli_name { #( #cli_fields, )* @@ -190,7 +188,6 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { #fn_from_cli_for_struct impl #name { - #(#fn_get_arg)* #(#vec_fn_input_arg)* fn try_parse() -> Result<#cli_name, clap::Error> { @@ -305,7 +302,7 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream { self::methods::from_cli_for_enum::from_cli_for_enum(ast, variants); let gen = quote! { - #[derive(Debug, Clone, clap::Parser, interactive_clap_derive::ToCliArgs)] + #[derive(Debug, Clone, clap::Parser, interactive_clap::ToCliArgs)] pub enum #cli_name { #( #enum_variants, )* } diff --git a/src/lib.rs b/src/lib.rs index 1e65808..42b2d3a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,13 +30,20 @@ pub trait ToCliArgs { fn to_cli_args(&self) -> std::collections::VecDeque; } +pub enum ResultFromCli { + Ok(T), + Cancel(Option), + Back, + Err(Option, E), +} + pub trait FromCli { type FromCliContext; type FromCliError; fn from_cli( optional_clap_variant: Option<::CliVariant>, context: Self::FromCliContext, - ) -> Result, Self::FromCliError> + ) -> ResultFromCli<::CliVariant, Self::FromCliError> where Self: Sized + ToCli; }