From 908e95f72822abc217161e1de92802bd2da8f91f Mon Sep 17 00:00:00 2001 From: Nathan West Date: Thu, 10 Oct 2024 15:28:37 -0400 Subject: [PATCH] Remove atty by migrating to clap 4 (#202) --- Cargo.lock | 327 +++++++++++++++++++++++++++++++++------------- cli/Cargo.toml | 9 +- cli/src/args.rs | 264 +++++++++++++++---------------------- cli/src/config.rs | 32 ++--- cli/src/main.rs | 162 ++++++++++++----------- cli/src/writer.rs | 40 +++--- 6 files changed, 471 insertions(+), 363 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1f35fcb9..e2d5e290 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,22 +27,60 @@ dependencies = [ ] [[package]] -name = "anyhow" -version = "1.0.82" +name = "anstream" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] [[package]] -name = "atty" -version = "0.2.14" +name = "anstyle" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ - "hermit-abi", - "libc", - "winapi", + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", ] +[[package]] +name = "anyhow" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" + [[package]] name = "autocfg" version = "1.2.0" @@ -51,9 +89,9 @@ checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "bitflags" -version = "1.3.2" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "bstr" @@ -94,63 +132,66 @@ dependencies = [ "js-sys", "num-traits", "wasm-bindgen", - "windows-targets", + "windows-targets 0.52.5", ] [[package]] name = "clap" -version = "3.2.25" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3" dependencies = [ - "atty", - "bitflags", - "clap_lex", - "indexmap 1.9.3", - "once_cell", - "strsim", - "termcolor", - "textwrap", + "clap_builder", + "clap_derive", ] [[package]] -name = "clap_complete" -version = "3.2.5" +name = "clap_builder" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f7a2e0a962c45ce25afce14220bc24f9dade0a1787f185cecf96bfba7847cd8" +checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b" dependencies = [ - "clap", + "anstream", + "anstyle", + "clap_lex", + "strsim", + "terminal_size", + "unicase", + "unicode-width", ] [[package]] -name = "clap_complete_command" -version = "0.3.4" +name = "clap_complete" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77159b3389b97ee638bb2a1d572069a7d9088a55e6281e3c76fc17b9cd51227" +checksum = "74a01f4f9ee6c066d42a1c8dedf0dcddad16c72a8981a309d6398de3a75b0c39" dependencies = [ "clap", - "clap_complete", - "clap_complete_fig", ] [[package]] -name = "clap_complete_fig" -version = "3.2.4" +name = "clap_derive" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed37b4c0c1214673eba6ad8ea31666626bf72be98ffb323067d973c48b4964b9" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ - "clap", - "clap_complete", + "heck", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "clap_lex" -version = "0.2.4" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" -dependencies = [ - "os_str_bytes", -] +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "cool_asserts" @@ -210,6 +251,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "expect-test" version = "1.5.0" @@ -253,12 +304,6 @@ dependencies = [ "regex-syntax", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.14.3" @@ -266,13 +311,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] -name = "hermit-abi" -version = "0.1.19" +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "iana-time-zone" @@ -319,16 +361,6 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cfe9645a18782869361d9c8732246be7b410ad4e919d3609ebabdac00ba12c3" -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - [[package]] name = "indexmap" version = "2.2.6" @@ -336,9 +368,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.12.1" @@ -381,6 +419,12 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + [[package]] name = "log" version = "0.4.21" @@ -399,7 +443,7 @@ version = "0.50.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -417,12 +461,6 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" -[[package]] -name = "os_str_bytes" -version = "6.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" - [[package]] name = "proc-macro2" version = "1.0.80" @@ -490,6 +528,19 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "ryu" version = "1.0.17" @@ -547,9 +598,9 @@ dependencies = [ [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" @@ -563,20 +614,15 @@ dependencies = [ ] [[package]] -name = "termcolor" -version = "1.4.1" +name = "terminal_size" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ - "winapi-util", + "rustix", + "windows-sys 0.48.0", ] -[[package]] -name = "textwrap" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" - [[package]] name = "thiserror" version = "1.0.58" @@ -624,7 +670,7 @@ version = "0.22.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" dependencies = [ - "indexmap 2.2.6", + "indexmap", "serde", "serde_spanned", "toml_datetime", @@ -655,7 +701,7 @@ version = "1.11.0" dependencies = [ "anyhow", "clap", - "clap_complete_command", + "clap_complete", "flexi_logger", "ignore", "log", @@ -685,12 +731,39 @@ dependencies = [ "thiserror", ] +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "walkdir" version = "2.5.0" @@ -792,7 +865,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets", + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", ] [[package]] @@ -801,7 +883,22 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -810,28 +907,46 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] +[[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_gnullvm" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +[[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_gnu" version = "0.52.5" @@ -844,24 +959,48 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +[[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_gnu" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +[[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_gnullvm" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.5" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 44f95a0d..9c06dfb6 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -15,8 +15,12 @@ path = "src/main.rs" go = [] [dependencies] -clap = { version = "3", features = ["cargo"] } -clap_complete_command = "0.3" +clap = { version = "4.5", features = [ + "cargo", + "derive", + "unicode", + "wrap_help", +] } ignore = "0.4" once_cell = "1" rayon = "1.10" @@ -26,3 +30,4 @@ typeshare-core = { path = "../core", version = "=1.11.0" } log.workspace = true flexi_logger.workspace = true anyhow = "1" +clap_complete = "4.5.32" diff --git a/cli/src/args.rs b/cli/src/args.rs index 790a94ba..31270f61 100644 --- a/cli/src/args.rs +++ b/cli/src/args.rs @@ -1,160 +1,108 @@ //! Command line argument parsing. -use clap::{command, Arg, ArgGroup, Command}; - -const VERSION: &str = env!("CARGO_PKG_VERSION"); - -pub const ARG_TYPE: &str = "TYPE"; -pub const ARG_SWIFT_PREFIX: &str = "SWIFTPREFIX"; -pub const ARG_KOTLIN_PREFIX: &str = "KOTLINPREFIX"; -pub const ARG_JAVA_PACKAGE: &str = "JAVAPACKAGE"; -pub const ARG_MODULE_NAME: &str = "MODULENAME"; -pub const ARG_SCALA_PACKAGE: &str = "SCALAPACKAGE"; -pub const ARG_SCALA_MODULE_NAME: &str = "SCALAMODULENAME"; -#[cfg(feature = "go")] -pub const ARG_GO_PACKAGE: &str = "GOPACKAGE"; -pub const ARG_CONFIG_FILE_NAME: &str = "CONFIGFILENAME"; -pub const ARG_GENERATE_CONFIG: &str = "generate-config-file"; -pub const ARG_OUTPUT_FILE: &str = "output-file"; -pub const ARG_OUTPUT_FOLDER: &str = "output-folder"; -pub const ARG_FOLLOW_LINKS: &str = "follow-links"; -pub const ARG_TARGET_OS: &str = "target_os"; - -#[cfg(feature = "go")] -const AVAILABLE_LANGUAGES: [&str; 5] = ["kotlin", "scala", "swift", "typescript", "go"]; - -#[cfg(not(feature = "go"))] -const AVAILABLE_LANGUAGES: [&str; 4] = ["kotlin", "scala", "swift", "typescript"]; - -/// Parse command line arguments. -pub(crate) fn build_command() -> Command<'static> { - command!("typeshare") - .version(VERSION) - .args_conflicts_with_subcommands(true) - .subcommand_negates_reqs(true) - .subcommand( - Command::new("completions") - .about("Generate shell completions") - .arg( - Arg::new("shell") - .value_name("SHELL") - .help("The shell to generate the completions for") - .required(true) - .possible_values(clap_complete_command::Shell::possible_values()), - ), - ) - .arg( - Arg::new(ARG_TYPE) - .short('l') - .long("lang") - .help("Language of generated types") - .takes_value(true) - .possible_values(AVAILABLE_LANGUAGES) - .required_unless(ARG_GENERATE_CONFIG), - ) - .arg( - Arg::new(ARG_SWIFT_PREFIX) - .short('s') - .long("swift-prefix") - .help("Prefix for generated Swift types") - .takes_value(true) - .required(false), - ) - .arg( - Arg::new(ARG_KOTLIN_PREFIX) - .short('k') - .long("kotlin-prefix") - .help("Prefix for generated Kotlin types") - .takes_value(true) - .required(false), - ) - .arg( - Arg::new(ARG_JAVA_PACKAGE) - .short('j') - .long("java-package") - .help("JAVA package name") - .takes_value(true) - .required(false), - ) - .arg( - Arg::new(ARG_MODULE_NAME) - .short('m') - .long("module-name") - .help("Kotlin serializer module name") - .takes_value(true) - .required(false), - ) - .arg( - Arg::new(ARG_SCALA_PACKAGE) - .long("scala-package") - .help("Scala package name") - .takes_value(true) - .required(false), - ) - .arg( - Arg::new(ARG_SCALA_MODULE_NAME) - .long("scala-module-name") - .help("Scala serializer module name") - .takes_value(true) - .required(false), - ) - - .arg( - Arg::new(ARG_CONFIG_FILE_NAME) - .short('c') - .long("config-file") - .help("Configuration file for typeshare") - .takes_value(true) - .required(false), - ) - .arg( - Arg::new(ARG_GENERATE_CONFIG) - .short('g') - .long("generate-config-file") - .help("Generates a configuration file based on the other options specified. The file will be written to typeshare.toml by default or to the file path specified by the --config-file option.") - .takes_value(false) - .required(false), - ) - .arg( - Arg::new(ARG_OUTPUT_FILE) - .short('o') - .long("output-file") - .help("File to write output to. mtime will be preserved if the file contents don't change") - .required_unless_present_any([ARG_GENERATE_CONFIG, ARG_OUTPUT_FOLDER]) - .takes_value(true) - .long(ARG_OUTPUT_FILE) - .conflicts_with(ARG_OUTPUT_FOLDER) - ) - .arg( - Arg::new(ARG_OUTPUT_FOLDER) - .short('d') - .long("output-folder") - .help("Folder to write output to. mtime will be preserved if the file contents don't change") - .required_unless_present_any([ARG_GENERATE_CONFIG, ARG_OUTPUT_FILE]) - .takes_value(true) - .long(ARG_OUTPUT_FOLDER) - .conflicts_with(ARG_OUTPUT_FILE) - ) - .group(ArgGroup::new("output").args(&["output-file", "output-folder"])) - .arg( - Arg::new(ARG_FOLLOW_LINKS) - .short('L') - .long("follow-links") - .help("Follow symbolic links to directories instead of ignoring them.") - .takes_value(false) - .required(false) - ) - .arg( - Arg::new("directories") - .help("Directories within which to recursively find and process rust files") - .required_unless(ARG_GENERATE_CONFIG) - .min_values(1), - ).arg( - Arg::new(ARG_TARGET_OS) - .short('t') - .long("target-os") - .help("Optional restrict to target_os") - .takes_value(true) - .multiple_values(true) - .required(false) - ) +use std::path::PathBuf; + +#[derive(Debug, Clone, Copy, clap::ValueEnum)] +#[non_exhaustive] +pub enum AvailableLanguage { + Kotlin, + Scala, + Swift, + Typescript, + #[cfg(feature = "go")] + Go, +} + +#[derive(clap::Parser)] +#[command( + version, + args_conflicts_with_subcommands = true, + subcommand_negates_reqs = true +)] +pub struct Args { + #[command(subcommand)] + pub subcommand: Option, + + /// Language of generated types + #[arg(short, long = "lang", required_unless_present = "generate_config")] + pub language: Option, + + /// Prefix for generated Swift types + #[arg(short, long)] + pub swift_prefix: Option, + + /// Prefix for generated Kotlin types + #[arg(short, long)] + pub kotlin_prefix: Option, + + /// JAVA package name + #[arg(short, long)] + pub java_package: Option, + + /// Kotlin serializer module name + #[arg(short = 'm', long = "module-name")] + pub kotlin_module_name: Option, + + /// Scala package name + #[arg(long)] + pub scala_package: Option, + + /// Scala serializer module name + #[arg(long)] + pub scala_module_name: Option, + + #[cfg(feature = "go")] + /// Go package name + #[arg(long)] + pub go_package: Option, + + /// Configuration file for typeshare + #[arg(short, long)] + pub config_file: Option, + + #[command(flatten)] + pub output: Output, + + /// Follow symbolic links to directories instead of ignoring them. + #[arg(short = 'L', long)] + pub follow_links: bool, + + /// Directories within which to recursively find and process rust files + #[arg(required=true, num_args = 1..)] + pub directories: Vec, + + /// Optional restrict to target_os + #[arg(short, long, num_args = 1..)] + pub target_os: Option>, +} + +#[derive(Debug, Clone, Copy, clap::Subcommand)] +pub enum Command { + /// Generate shell completions + Completions { + /// The shell to generate the completions for + shell: clap_complete::Shell, + }, +} + +#[derive(clap::Args, Debug)] +#[group(multiple = false, required = true)] +pub struct Output { + /// File to write output to. mtime will be preserved if the file contents + /// don't change + #[arg(short = 'o', long = "output-file")] + pub file: Option, + + /// Folder to write output to. mtime will be preserved if the file contents + /// don't change + #[arg(short = 'd', long = "output-folder")] + pub folder: Option, + + // If given, we're going to output a new template configuration file + // instead of running typeshare normally, so we make it mutually exclusive + // with running normally + /// Generates a configuration file based on the other options specified. + /// The file will be written to typeshare.toml by default or to the file + /// path specified by the --config-file option. + #[arg(short, long)] + pub generate_config: bool, } diff --git a/cli/src/config.rs b/cli/src/config.rs index 51de47b3..08b58a2b 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -1,6 +1,7 @@ use anyhow::Context; use serde::{Deserialize, Serialize}; use std::{ + borrow::Cow, collections::HashMap, env, fs::{self, OpenOptions}, @@ -69,8 +70,8 @@ pub(crate) struct Config { pub target_os: Vec, } -pub(crate) fn store_config(config: &Config, file_path: Option<&str>) -> anyhow::Result<()> { - let file_path = file_path.unwrap_or(DEFAULT_CONFIG_FILE_NAME); +pub(crate) fn store_config(config: &Config, file_path: Option<&Path>) -> anyhow::Result<()> { + let file_path = file_path.unwrap_or(Path::new(DEFAULT_CONFIG_FILE_NAME)); let config_output = toml::to_string_pretty(config).context("Failed to serialize to toml")?; // Fail if trying to overwrite an existing config file @@ -84,13 +85,10 @@ pub(crate) fn store_config(config: &Config, file_path: Option<&str>) -> anyhow:: Ok(()) } -pub(crate) fn load_config

(file_path: Option

) -> Result -where - PathBuf: From

, -{ +pub(crate) fn load_config(file_path: Option<&Path>) -> Result { let file_path = file_path - .map(PathBuf::from) - .or_else(find_configuration_file); + .map(Cow::Borrowed) + .or_else(|| find_configuration_file().map(Cow::Owned)); if let Some(file_path) = file_path { let config_string = fs::read_to_string(file_path)?; @@ -105,6 +103,8 @@ fn find_configuration_file() -> Option { let mut path = env::current_dir().ok()?; let file = Path::new(DEFAULT_CONFIG_FILE_NAME); + // TODO: I want to use `path.ancestors` here but it would requiring + // allocating on every loop iteration and that makes me sad. loop { path.push(file); @@ -130,7 +130,7 @@ mod test { #[test] fn to_string_and_back() { let path = config_file_path("mappings_config.toml"); - let config = load_config(Some(path)).unwrap(); + let config = load_config(Some(&path)).unwrap(); toml::from_str::(&toml::to_string_pretty(&config).unwrap()).unwrap(); } @@ -138,7 +138,7 @@ mod test { #[test] fn default_test() { let path = config_file_path("default_config.toml"); - let config = load_config(Some(path)).unwrap(); + let config = load_config(Some(&path)).unwrap(); assert_eq!(config, Config::default()); } @@ -146,7 +146,7 @@ mod test { #[test] fn empty_test() { let path = config_file_path("empty_config.toml"); - let config = load_config(Some(path)).unwrap(); + let config = load_config(Some(&path)).unwrap(); assert_eq!(config, Config::default()); } @@ -154,7 +154,7 @@ mod test { #[test] fn mappings_test() { let path = config_file_path("mappings_config.toml"); - let config = load_config(Some(path)).unwrap(); + let config = load_config(Some(&path)).unwrap(); assert_eq!(config.swift.type_mappings["DateTime"], "Date"); assert_eq!(config.kotlin.type_mappings["DateTime"], "String"); @@ -167,7 +167,7 @@ mod test { #[test] fn decorators_test() { let path = config_file_path("decorators_config.toml"); - let config = load_config(Some(path)).unwrap(); + let config = load_config(Some(&path)).unwrap(); assert_eq!(config.swift.default_decorators.len(), 1); assert_eq!(config.swift.default_decorators[0], "Sendable"); @@ -176,7 +176,7 @@ mod test { #[test] fn constraints_test() { let path = config_file_path("constraints_config.toml"); - let config = load_config(Some(path)).unwrap(); + let config = load_config(Some(&path)).unwrap(); assert_eq!(config.swift.default_generic_constraints.len(), 1); assert_eq!(config.swift.default_generic_constraints[0], "Sendable"); @@ -185,7 +185,7 @@ mod test { #[test] fn swift_prefix_test() { let path = config_file_path("swift_prefix_config.toml"); - let config = load_config(Some(path)).unwrap(); + let config = load_config(Some(&path)).unwrap(); assert_eq!(config.swift.prefix, "test"); } @@ -193,7 +193,7 @@ mod test { #[cfg(feature = "go")] fn go_package_test() { let path = config_file_path("go_config.toml"); - let config = load_config(Some(path)).unwrap(); + let config = load_config(Some(&path)).unwrap(); assert_eq!(config.go.package, "testPackage"); } diff --git a/cli/src/main.rs b/cli/src/main.rs index a431acda..17477a0a 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,19 +1,24 @@ //! This is the command line tool for Typeshare. It is used to generate source files in other //! languages based on Rust code. +//! -use anyhow::{anyhow, Context}; -use args::{ - build_command, ARG_CONFIG_FILE_NAME, ARG_FOLLOW_LINKS, ARG_GENERATE_CONFIG, ARG_JAVA_PACKAGE, - ARG_KOTLIN_PREFIX, ARG_MODULE_NAME, ARG_OUTPUT_FOLDER, ARG_SCALA_MODULE_NAME, - ARG_SCALA_PACKAGE, ARG_SWIFT_PREFIX, ARG_TARGET_OS, ARG_TYPE, +mod args; +mod config; +mod parse; +mod writer; + +use std::{ + collections::{BTreeMap, HashMap}, + io, + path::Path, }; -use clap::ArgMatches; -use config::Config; + +use anyhow::{anyhow, Context}; +use clap::{CommandFactory, Parser}; +use clap_complete::Generator; use ignore::{overrides::OverrideBuilder, types::TypesBuilder, WalkBuilder}; use log::error; -use parse::{all_types, parse_input, parser_inputs}; use rayon::iter::ParallelBridge; -use std::collections::{BTreeMap, HashMap}; #[cfg(feature = "go")] use typeshare_core::language::Go; use typeshare_core::{ @@ -23,12 +28,13 @@ use typeshare_core::{ }, parser::ParsedData, }; -use writer::write_generated; -mod args; -mod config; -mod parse; -mod writer; +use crate::{ + args::{Args, Command}, + config::Config, + parse::{all_types, parse_input, parser_inputs}, + writer::{write_generated, Output}, +}; fn main() -> anyhow::Result<()> { flexi_logger::Logger::try_with_env() @@ -36,49 +42,44 @@ fn main() -> anyhow::Result<()> { .start() .unwrap(); - #[allow(unused_mut)] - let mut command = build_command(); + let options = Args::parse(); - #[cfg(feature = "go")] - { - command = command.arg( - clap::Arg::new(args::ARG_GO_PACKAGE) - .long("go-package") - .help("Go package name") - .takes_value(true) - .required(false), - ); - } - - let options = command.get_matches(); - - if let Some(options) = options.subcommand_matches("completions") { - let shell = options - .value_of_t::("shell") - .context("Missing shell argument")?; + if let Some(options) = options.subcommand { + match options { + Command::Completions { shell } => { + shell.generate(&mut Args::command(), &mut io::stdout().lock()) + } + } - let mut command = build_command(); - shell.generate(&mut command, &mut std::io::stdout()); + return Ok(()); } - let config_file = options.value_of(ARG_CONFIG_FILE_NAME); - let config = config::load_config(config_file).context("Unable to read configuration file")?; - let config = override_configuration(config, &options)?; + // Note that this can be `None`; the relevant functions handle this case + // on their own. + let config_file = options.config_file.as_deref(); - if options.is_present(ARG_GENERATE_CONFIG) { + if options.output.generate_config { let config = override_configuration(Config::default(), &options)?; - let file_path = options.value_of(ARG_CONFIG_FILE_NAME); - config::store_config(&config, file_path).context("Failed to create new config file")?; + config::store_config(&config, config_file).context("Failed to create new config file")?; + return Ok(()); } - let mut directories = options - .values_of("directories") - .ok_or_else(|| anyhow!("missing directories argument"))?; + let config = config::load_config(config_file).context("Unable to read configuration file")?; + let config = override_configuration(config, &options)?; - let language_type = options - .value_of(ARG_TYPE) - .and_then(|lang| lang.parse::().ok()) - .ok_or_else(|| anyhow!("argument parser didn't validate ARG_TYPE correctly"))?; + let directories = options.directories.as_slice(); + + let language_type = match options.language { + None => panic!("no language specified; `clap` should have guaranteed its presence"), + Some(language) => match language { + args::AvailableLanguage::Kotlin => SupportedLanguage::Kotlin, + args::AvailableLanguage::Scala => SupportedLanguage::Scala, + args::AvailableLanguage::Swift => SupportedLanguage::Swift, + args::AvailableLanguage::Typescript => SupportedLanguage::TypeScript, + #[cfg(feature = "go")] + args::AvailableLanguage::Go => SupportedLanguage::Go, + }, + }; let mut types = TypesBuilder::new(); types @@ -88,8 +89,8 @@ fn main() -> anyhow::Result<()> { // This is guaranteed to always have at least one value by the clap configuration let first_root = directories - .next() - .ok_or_else(|| anyhow!("directories is empty"))?; + .first() + .expect("directories is empty; this shouldn't be possible"); let overrides = OverrideBuilder::new(first_root) // Don't process files inside of tools/typeshare/ @@ -100,20 +101,31 @@ fn main() -> anyhow::Result<()> { let mut walker_builder = WalkBuilder::new(first_root); // Sort walker output for deterministic output across platforms - walker_builder.sort_by_file_path(|a, b| a.cmp(b)); - walker_builder.types(types.build().context("Failed to build types")?); - walker_builder.overrides(overrides); - walker_builder.follow_links(options.is_present(ARG_FOLLOW_LINKS)); + walker_builder + .sort_by_file_path(Path::cmp) + .types(types.build().context("Failed to build types")?) + .overrides(overrides) + .follow_links(options.follow_links); for root in directories { walker_builder.add(root); } - let multi_file = options.value_of(ARG_OUTPUT_FOLDER).is_some(); + let destination = if let Some(ref file) = options.output.file { + Output::File(file) + } else if let Some(ref folder) = options.output.folder { + Output::Folder(folder) + } else { + panic!( + "Got neither a file nor a folder to output to; this indicates a + bug in typeshare, since `clap` is supposed to prevent this" + ) + }; + let multi_file = matches!(destination, Output::Folder(_)); let target_os = config.target_os.clone(); - let lang = language(language_type, config, multi_file); + let mut lang = language(language_type, config, multi_file); let ignored_types = lang.ignored_reference_types(); // The walker ignores directories that are git-ignored. If you need @@ -141,7 +153,12 @@ fn main() -> anyhow::Result<()> { }; check_parse_errors(&crate_parsed_data)?; - write_generated(options, lang, crate_parsed_data, import_candidates)?; + write_generated( + destination, + lang.as_mut(), + crate_parsed_data, + import_candidates, + )?; Ok(()) } @@ -197,42 +214,41 @@ fn language( } /// Overrides any configuration values with provided arguments -fn override_configuration(mut config: Config, options: &ArgMatches) -> anyhow::Result { - if let Some(swift_prefix) = options.value_of(ARG_SWIFT_PREFIX) { - config.swift.prefix = swift_prefix.to_string(); +fn override_configuration(mut config: Config, options: &Args) -> anyhow::Result { + if let Some(swift_prefix) = options.swift_prefix.as_ref() { + config.swift.prefix = swift_prefix.clone(); } - if let Some(kotlin_prefix) = options.value_of(ARG_KOTLIN_PREFIX) { - config.kotlin.prefix = kotlin_prefix.to_string(); + if let Some(kotlin_prefix) = options.kotlin_prefix.as_ref() { + config.kotlin.prefix = kotlin_prefix.clone(); } - if let Some(java_package) = options.value_of(ARG_JAVA_PACKAGE) { - config.kotlin.package = java_package.to_string(); + if let Some(java_package) = options.java_package.as_ref() { + config.kotlin.package = java_package.clone(); } - if let Some(module_name) = options.value_of(ARG_MODULE_NAME) { + if let Some(module_name) = options.kotlin_module_name.as_ref() { config.kotlin.module_name = module_name.to_string(); } - if let Some(scala_package) = options.value_of(ARG_SCALA_PACKAGE) { - config.scala.package = scala_package.to_string(); + if let Some(scala_package) = options.scala_package.as_ref() { + config.scala.package = scala_package.clone(); } - if let Some(scala_module_name) = options.value_of(ARG_SCALA_MODULE_NAME) { + if let Some(scala_module_name) = options.scala_module_name.as_ref() { config.scala.module_name = scala_module_name.to_string(); } #[cfg(feature = "go")] { - if let Some(go_package) = options.value_of(args::ARG_GO_PACKAGE) { + if let Some(go_package) = options.go_package.as_ref() { config.go.package = go_package.to_string(); } assert_go_package_present(&config)?; } - config.target_os = options - .get_many::(ARG_TARGET_OS) - .map(|arg| arg.into_iter().map(ToString::to_string).collect::>()) - .unwrap_or_default(); + + config.target_os = options.target_os.as_deref().unwrap_or_default().to_vec(); + Ok(config) } diff --git a/cli/src/writer.rs b/cli/src/writer.rs index 011ec1fe..413e91b4 100644 --- a/cli/src/writer.rs +++ b/cli/src/writer.rs @@ -1,41 +1,41 @@ //! Generated source file output. -use crate::args::{ARG_OUTPUT_FILE, ARG_OUTPUT_FOLDER}; use anyhow::Context; -use clap::ArgMatches; use log::info; use std::{ collections::{BTreeMap, HashMap}, fs, - path::{Path, PathBuf}, + path::Path, }; use typeshare_core::{ language::{CrateName, CrateTypes, Language, SINGLE_FILE_CRATE_NAME}, parser::ParsedData, }; +#[derive(Debug, Clone, Copy)] +pub enum Output<'a> { + File(&'a Path), + Folder(&'a Path), +} + /// Write the parsed data to the one or more files depending on command line options. pub fn write_generated( - options: ArgMatches, - lang: Box, + destination: Output<'_>, + lang: &mut (impl Language + ?Sized), crate_parsed_data: BTreeMap, import_candidates: CrateTypes, ) -> Result<(), anyhow::Error> { - let output_folder = options.value_of(ARG_OUTPUT_FOLDER); - let output_file = options.value_of(ARG_OUTPUT_FILE); - - if let Some(folder) = output_folder { - write_multiple_files(lang, folder, crate_parsed_data, import_candidates) - } else if let Some(file) = output_file { - write_single_file(lang, file, crate_parsed_data) - } else { - Ok(()) + match destination { + Output::File(path) => write_single_file(lang, path, crate_parsed_data), + Output::Folder(path) => { + write_multiple_files(lang, path, crate_parsed_data, import_candidates) + } } } /// Write multiple module files. fn write_multiple_files( - mut lang: Box, - output_folder: &str, + lang: &mut (impl Language + ?Sized), + output_folder: &Path, crate_parsed_data: BTreeMap, import_candidates: CrateTypes, ) -> Result<(), anyhow::Error> { @@ -46,14 +46,14 @@ fn write_multiple_files( check_write_file(&outfile, generated_contents)?; } - lang.post_generation(output_folder) + lang.post_generation(&output_folder.as_os_str().to_string_lossy()) .context("Post generation failed")?; Ok(()) } /// Write the file if the contents have changed. -fn check_write_file(outfile: &PathBuf, output: Vec) -> anyhow::Result<()> { +fn check_write_file(outfile: &Path, output: Vec) -> anyhow::Result<()> { match fs::read(outfile) { Ok(buf) if buf == output => { // avoid writing the file to leave the mtime intact @@ -82,8 +82,8 @@ fn check_write_file(outfile: &PathBuf, output: Vec) -> anyhow::Result<()> { /// Write all types to a single file. fn write_single_file( - mut lang: Box, - file_name: &str, + lang: &mut (impl Language + ?Sized), + file_name: &Path, mut crate_parsed_data: BTreeMap, ) -> Result<(), anyhow::Error> { let parsed_data = crate_parsed_data