├── .editorconfig ├── .github ├── FUNDING.yml └── workflows │ ├── clippy.yml │ ├── coverage.yml │ ├── rustfmt.yml │ └── test.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── dhall ├── Cargo.toml ├── cli.dhall ├── color.dhall ├── point.dhall └── src │ └── main.rs ├── docs ├── 00-install.md ├── 01-syntax.md ├── 02-pos-arg.md ├── 03-arg-opt.md └── 04-opt.md ├── example ├── Cargo.toml ├── build.rs ├── opts │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── src │ └── main.rs ├── macro ├── Cargo.toml └── src │ ├── deriver.rs │ ├── deriver │ ├── arg_opt.rs │ ├── cli.rs │ ├── from_kebab_str.rs │ ├── multi_select.rs │ ├── opt.rs │ ├── pos_arg.rs │ ├── single_select.rs │ └── snapshots │ │ ├── cli_compose_macro__deriver__arg_opt__tests___enum.snap │ │ ├── cli_compose_macro__deriver__arg_opt__tests___union.snap │ │ ├── cli_compose_macro__deriver__arg_opt__tests__empty.snap │ │ ├── cli_compose_macro__deriver__arg_opt__tests__struct_with_multiple_fields.snap │ │ ├── cli_compose_macro__deriver__arg_opt__tests__struct_with_single_field.snap │ │ ├── cli_compose_macro__deriver__arg_opt__tests__struct_without_field.snap │ │ ├── cli_compose_macro__deriver__cli__tests__empty.snap │ │ ├── cli_compose_macro__deriver__cli__tests__empty_struct.snap │ │ ├── cli_compose_macro__deriver__from_kebab_str__tests___enum.snap │ │ ├── cli_compose_macro__deriver__from_kebab_str__tests___struct.snap │ │ ├── cli_compose_macro__deriver__from_kebab_str__tests___union.snap │ │ ├── cli_compose_macro__deriver__from_kebab_str__tests__empty.snap │ │ ├── cli_compose_macro__deriver__opt__tests__empty.snap │ │ ├── cli_compose_macro__deriver__pos_arg__tests__empty.snap │ │ ├── cli_compose_macro__deriver__pos_arg__tests__struct_with_multiple_fields.snap │ │ ├── cli_compose_macro__deriver__pos_arg__tests__struct_with_single_field.snap │ │ └── cli_compose_macro__deriver__pos_arg__tests__struct_without_field.snap │ ├── doc.rs │ ├── lib.rs │ ├── pretty_print.rs │ ├── snapshots │ └── cli_compose_macro__use_cli__tests__use_cli.snap │ └── use_cli.rs ├── renovate.json ├── src ├── codegen.rs ├── lib.rs ├── runtime.rs ├── schema_impl.rs └── snapshots │ ├── cli_compose__runtime__tests__long_flag.snap │ └── cli_compose__runtime__tests__short_flags.snap └── tarpaulin.toml /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | insert_final_newline = true 8 | indent_size = 4 9 | indent_style = space 10 | 11 | [*.yml] 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: 0918nobita 2 | -------------------------------------------------------------------------------- /.github/workflows/clippy.yml: -------------------------------------------------------------------------------- 1 | name: Clippy 2 | 3 | on: push 4 | 5 | jobs: 6 | clippy: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - uses: actions-rs/toolchain@v1 11 | with: 12 | toolchain: stable 13 | components: clippy 14 | - uses: actions-rs/cargo@v1 15 | with: 16 | command: clippy 17 | args: -- -D warnings 18 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: Coverage 2 | 3 | on: push 4 | 5 | jobs: 6 | coverage: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - uses: actions-rs/toolchain@v1 11 | with: 12 | toolchain: nightly 13 | - uses: actions-rs/install@v0.1.2 14 | with: 15 | crate: cargo-tarpaulin 16 | - uses: actions-rs/cargo@v1 17 | with: 18 | command: tarpaulin 19 | - uses: codecov/codecov-action@v3 20 | with: 21 | fail_ci_if_error: true 22 | -------------------------------------------------------------------------------- /.github/workflows/rustfmt.yml: -------------------------------------------------------------------------------- 1 | name: Rustfmt 2 | 3 | on: push 4 | 5 | jobs: 6 | rustfmt: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - uses: actions-rs/toolchain@v1 11 | with: 12 | toolchain: stable 13 | components: rustfmt 14 | - uses: actions-rs/cargo@v1 15 | with: 16 | command: fmt 17 | args: --all --check 18 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: push 4 | 5 | jobs: 6 | macro: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - uses: actions-rs/toolchain@v1 11 | with: 12 | toolchain: stable 13 | - uses: actions-rs/cargo@v1 14 | with: 15 | command: test 16 | args: -p cli-compose-macro --verbose 17 | 18 | codegen-feature: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v3 22 | - uses: actions-rs/toolchain@v1 23 | with: 24 | toolchain: stable 25 | - uses: actions-rs/cargo@v1 26 | with: 27 | command: test 28 | args: -p cli-compose --features codegen --verbose 29 | 30 | runtime-feature: 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@v3 34 | - uses: actions-rs/toolchain@v1 35 | with: 36 | toolchain: stable 37 | - uses: actions-rs/cargo@v1 38 | with: 39 | command: test 40 | args: -p cli-compose --features runtime --verbose 41 | 42 | schema-feature: 43 | runs-on: ubuntu-latest 44 | steps: 45 | - uses: actions/checkout@v3 46 | - uses: actions-rs/toolchain@v1 47 | with: 48 | toolchain: stable 49 | - uses: actions-rs/cargo@v1 50 | with: 51 | command: test 52 | args: -p cli-compose --features schema --verbose 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | # Coverage Report 3 | *.xml 4 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "abnf" 7 | version = "0.6.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "47feb9fbcef700639ef28e04ca2a87eab8161a01a075ee227b15c90143805462" 10 | dependencies = [ 11 | "nom", 12 | ] 13 | 14 | [[package]] 15 | name = "abnf_to_pest" 16 | version = "0.5.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "372baaa5d3a422d8816b513bcdb2c120078c8614f7ecbcc3baf34a1634bbbe2e" 19 | dependencies = [ 20 | "abnf", 21 | "indexmap", 22 | "itertools", 23 | "pretty", 24 | ] 25 | 26 | [[package]] 27 | name = "annotate-snippets" 28 | version = "0.9.1" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "c3b9d411ecbaf79885c6df4d75fff75858d5995ff25385657a28af47e82f9c36" 31 | dependencies = [ 32 | "unicode-width", 33 | ] 34 | 35 | [[package]] 36 | name = "anyhow" 37 | version = "1.0.58" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" 40 | 41 | [[package]] 42 | name = "arrayvec" 43 | version = "0.5.2" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" 46 | 47 | [[package]] 48 | name = "autocfg" 49 | version = "1.1.0" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 52 | 53 | [[package]] 54 | name = "bae" 55 | version = "0.1.7" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "33b8de67cc41132507eeece2584804efcb15f85ba516e34c944b7667f480397a" 58 | dependencies = [ 59 | "heck", 60 | "proc-macro-error", 61 | "proc-macro2", 62 | "quote", 63 | "syn", 64 | ] 65 | 66 | [[package]] 67 | name = "base64" 68 | version = "0.13.0" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" 71 | 72 | [[package]] 73 | name = "bitflags" 74 | version = "1.3.2" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 77 | 78 | [[package]] 79 | name = "block-buffer" 80 | version = "0.7.3" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" 83 | dependencies = [ 84 | "block-padding", 85 | "byte-tools", 86 | "byteorder", 87 | "generic-array 0.12.4", 88 | ] 89 | 90 | [[package]] 91 | name = "block-buffer" 92 | version = "0.9.0" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" 95 | dependencies = [ 96 | "generic-array 0.14.5", 97 | ] 98 | 99 | [[package]] 100 | name = "block-padding" 101 | version = "0.1.5" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" 104 | dependencies = [ 105 | "byte-tools", 106 | ] 107 | 108 | [[package]] 109 | name = "bumpalo" 110 | version = "3.10.0" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" 113 | 114 | [[package]] 115 | name = "byte-tools" 116 | version = "0.3.1" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" 119 | 120 | [[package]] 121 | name = "byteorder" 122 | version = "1.4.3" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 125 | 126 | [[package]] 127 | name = "bytes" 128 | version = "1.1.0" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" 131 | 132 | [[package]] 133 | name = "cc" 134 | version = "1.0.73" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 137 | 138 | [[package]] 139 | name = "cfg-if" 140 | version = "1.0.0" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 143 | 144 | [[package]] 145 | name = "cli-compose" 146 | version = "0.1.0-alpha.0" 147 | dependencies = [ 148 | "anyhow", 149 | "cli-compose-macro", 150 | "convert_case 0.5.0", 151 | "derive_more", 152 | "insta", 153 | "proc-macro2", 154 | "quote", 155 | "str-macro", 156 | "syn", 157 | "thiserror", 158 | ] 159 | 160 | [[package]] 161 | name = "cli-compose-dhall" 162 | version = "0.1.0" 163 | dependencies = [ 164 | "serde", 165 | "serde_dhall", 166 | ] 167 | 168 | [[package]] 169 | name = "cli-compose-macro" 170 | version = "0.1.0-alpha.0" 171 | dependencies = [ 172 | "anyhow", 173 | "bae", 174 | "convert_case 0.5.0", 175 | "derive_more", 176 | "insta", 177 | "proc-macro2", 178 | "quote", 179 | "syn", 180 | ] 181 | 182 | [[package]] 183 | name = "console" 184 | version = "0.15.0" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "a28b32d32ca44b70c3e4acd7db1babf555fa026e385fb95f18028f88848b3c31" 187 | dependencies = [ 188 | "encode_unicode", 189 | "libc", 190 | "once_cell", 191 | "terminal_size", 192 | "winapi", 193 | ] 194 | 195 | [[package]] 196 | name = "convert_case" 197 | version = "0.4.0" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" 200 | 201 | [[package]] 202 | name = "convert_case" 203 | version = "0.5.0" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "fb4a24b1aaf0fd0ce8b45161144d6f42cd91677fd5940fd431183eb023b3a2b8" 206 | 207 | [[package]] 208 | name = "core-foundation" 209 | version = "0.9.3" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" 212 | dependencies = [ 213 | "core-foundation-sys", 214 | "libc", 215 | ] 216 | 217 | [[package]] 218 | name = "core-foundation-sys" 219 | version = "0.8.3" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" 222 | 223 | [[package]] 224 | name = "cpufeatures" 225 | version = "0.2.2" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" 228 | dependencies = [ 229 | "libc", 230 | ] 231 | 232 | [[package]] 233 | name = "derive_more" 234 | version = "0.99.17" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" 237 | dependencies = [ 238 | "convert_case 0.4.0", 239 | "proc-macro2", 240 | "quote", 241 | "rustc_version", 242 | "syn", 243 | ] 244 | 245 | [[package]] 246 | name = "dhall" 247 | version = "0.11.1" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "9093ee48621ca9db16cd4948c7acf24a8ecc9af41cc9e226e39ea719df06d8b5" 250 | dependencies = [ 251 | "abnf_to_pest", 252 | "annotate-snippets", 253 | "elsa", 254 | "hex", 255 | "home", 256 | "itertools", 257 | "lazy_static", 258 | "once_cell", 259 | "percent-encoding", 260 | "pest", 261 | "pest_consume", 262 | "pest_generator", 263 | "quote", 264 | "reqwest", 265 | "serde", 266 | "serde_cbor", 267 | "sha2", 268 | "url", 269 | ] 270 | 271 | [[package]] 272 | name = "dhall_proc_macros" 273 | version = "0.6.0" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "df7c81d16870879ef530b07cef32bc6088f98937ab4168106cc8e382a05146bf" 276 | dependencies = [ 277 | "proc-macro2", 278 | "quote", 279 | "syn", 280 | ] 281 | 282 | [[package]] 283 | name = "digest" 284 | version = "0.8.1" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" 287 | dependencies = [ 288 | "generic-array 0.12.4", 289 | ] 290 | 291 | [[package]] 292 | name = "digest" 293 | version = "0.9.0" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" 296 | dependencies = [ 297 | "generic-array 0.14.5", 298 | ] 299 | 300 | [[package]] 301 | name = "doc-comment" 302 | version = "0.3.3" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" 305 | 306 | [[package]] 307 | name = "either" 308 | version = "1.6.1" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 311 | 312 | [[package]] 313 | name = "elsa" 314 | version = "1.7.0" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "2b4b5d23ed6b6948d68240aafa4ac98e568c9a020efd9d4201a6288bc3006e09" 317 | dependencies = [ 318 | "stable_deref_trait", 319 | ] 320 | 321 | [[package]] 322 | name = "encode_unicode" 323 | version = "0.3.6" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 326 | 327 | [[package]] 328 | name = "encoding_rs" 329 | version = "0.8.31" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" 332 | dependencies = [ 333 | "cfg-if", 334 | ] 335 | 336 | [[package]] 337 | name = "example" 338 | version = "0.1.0-alpha.0" 339 | dependencies = [ 340 | "cli-compose", 341 | "example-opts", 342 | ] 343 | 344 | [[package]] 345 | name = "example-opts" 346 | version = "0.1.0-alpha.0" 347 | dependencies = [ 348 | "cli-compose", 349 | ] 350 | 351 | [[package]] 352 | name = "fake-simd" 353 | version = "0.1.2" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" 356 | 357 | [[package]] 358 | name = "fastrand" 359 | version = "1.7.0" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" 362 | dependencies = [ 363 | "instant", 364 | ] 365 | 366 | [[package]] 367 | name = "fnv" 368 | version = "1.0.7" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 371 | 372 | [[package]] 373 | name = "foreign-types" 374 | version = "0.3.2" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 377 | dependencies = [ 378 | "foreign-types-shared", 379 | ] 380 | 381 | [[package]] 382 | name = "foreign-types-shared" 383 | version = "0.1.1" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 386 | 387 | [[package]] 388 | name = "form_urlencoded" 389 | version = "1.0.1" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" 392 | dependencies = [ 393 | "matches", 394 | "percent-encoding", 395 | ] 396 | 397 | [[package]] 398 | name = "futures-channel" 399 | version = "0.3.21" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" 402 | dependencies = [ 403 | "futures-core", 404 | ] 405 | 406 | [[package]] 407 | name = "futures-core" 408 | version = "0.3.21" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" 411 | 412 | [[package]] 413 | name = "futures-io" 414 | version = "0.3.21" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" 417 | 418 | [[package]] 419 | name = "futures-sink" 420 | version = "0.3.21" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" 423 | 424 | [[package]] 425 | name = "futures-task" 426 | version = "0.3.21" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" 429 | 430 | [[package]] 431 | name = "futures-util" 432 | version = "0.3.21" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" 435 | dependencies = [ 436 | "futures-core", 437 | "futures-io", 438 | "futures-task", 439 | "memchr", 440 | "pin-project-lite", 441 | "pin-utils", 442 | "slab", 443 | ] 444 | 445 | [[package]] 446 | name = "generic-array" 447 | version = "0.12.4" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" 450 | dependencies = [ 451 | "typenum", 452 | ] 453 | 454 | [[package]] 455 | name = "generic-array" 456 | version = "0.14.5" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" 459 | dependencies = [ 460 | "typenum", 461 | "version_check", 462 | ] 463 | 464 | [[package]] 465 | name = "h2" 466 | version = "0.3.13" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" 469 | dependencies = [ 470 | "bytes", 471 | "fnv", 472 | "futures-core", 473 | "futures-sink", 474 | "futures-util", 475 | "http", 476 | "indexmap", 477 | "slab", 478 | "tokio", 479 | "tokio-util", 480 | "tracing", 481 | ] 482 | 483 | [[package]] 484 | name = "half" 485 | version = "1.8.2" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" 488 | 489 | [[package]] 490 | name = "hashbrown" 491 | version = "0.11.2" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 494 | 495 | [[package]] 496 | name = "heck" 497 | version = "0.3.3" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 500 | dependencies = [ 501 | "unicode-segmentation", 502 | ] 503 | 504 | [[package]] 505 | name = "hermit-abi" 506 | version = "0.1.19" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 509 | dependencies = [ 510 | "libc", 511 | ] 512 | 513 | [[package]] 514 | name = "hex" 515 | version = "0.4.3" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 518 | 519 | [[package]] 520 | name = "home" 521 | version = "0.5.3" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654" 524 | dependencies = [ 525 | "winapi", 526 | ] 527 | 528 | [[package]] 529 | name = "http" 530 | version = "0.2.7" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb" 533 | dependencies = [ 534 | "bytes", 535 | "fnv", 536 | "itoa", 537 | ] 538 | 539 | [[package]] 540 | name = "http-body" 541 | version = "0.4.5" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" 544 | dependencies = [ 545 | "bytes", 546 | "http", 547 | "pin-project-lite", 548 | ] 549 | 550 | [[package]] 551 | name = "httparse" 552 | version = "1.7.1" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" 555 | 556 | [[package]] 557 | name = "httpdate" 558 | version = "1.0.2" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" 561 | 562 | [[package]] 563 | name = "hyper" 564 | version = "0.14.19" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "42dc3c131584288d375f2d07f822b0cb012d8c6fb899a5b9fdb3cb7eb9b6004f" 567 | dependencies = [ 568 | "bytes", 569 | "futures-channel", 570 | "futures-core", 571 | "futures-util", 572 | "h2", 573 | "http", 574 | "http-body", 575 | "httparse", 576 | "httpdate", 577 | "itoa", 578 | "pin-project-lite", 579 | "socket2", 580 | "tokio", 581 | "tower-service", 582 | "tracing", 583 | "want", 584 | ] 585 | 586 | [[package]] 587 | name = "hyper-tls" 588 | version = "0.5.0" 589 | source = "registry+https://github.com/rust-lang/crates.io-index" 590 | checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" 591 | dependencies = [ 592 | "bytes", 593 | "hyper", 594 | "native-tls", 595 | "tokio", 596 | "tokio-native-tls", 597 | ] 598 | 599 | [[package]] 600 | name = "idna" 601 | version = "0.2.3" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" 604 | dependencies = [ 605 | "matches", 606 | "unicode-bidi", 607 | "unicode-normalization", 608 | ] 609 | 610 | [[package]] 611 | name = "indexmap" 612 | version = "1.8.2" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a" 615 | dependencies = [ 616 | "autocfg", 617 | "hashbrown", 618 | ] 619 | 620 | [[package]] 621 | name = "insta" 622 | version = "1.15.0" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "4126dd76ebfe2561486a1bd6738a33d2029ffb068a99ac446b7f8c77b2e58dbc" 625 | dependencies = [ 626 | "console", 627 | "once_cell", 628 | "serde", 629 | "serde_json", 630 | "serde_yaml", 631 | "similar", 632 | ] 633 | 634 | [[package]] 635 | name = "instant" 636 | version = "0.1.12" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 639 | dependencies = [ 640 | "cfg-if", 641 | ] 642 | 643 | [[package]] 644 | name = "ipnet" 645 | version = "2.5.0" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" 648 | 649 | [[package]] 650 | name = "itertools" 651 | version = "0.9.0" 652 | source = "registry+https://github.com/rust-lang/crates.io-index" 653 | checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" 654 | dependencies = [ 655 | "either", 656 | ] 657 | 658 | [[package]] 659 | name = "itoa" 660 | version = "1.0.2" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" 663 | 664 | [[package]] 665 | name = "js-sys" 666 | version = "0.3.57" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" 669 | dependencies = [ 670 | "wasm-bindgen", 671 | ] 672 | 673 | [[package]] 674 | name = "lazy_static" 675 | version = "1.4.0" 676 | source = "registry+https://github.com/rust-lang/crates.io-index" 677 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 678 | 679 | [[package]] 680 | name = "lexical-core" 681 | version = "0.7.6" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" 684 | dependencies = [ 685 | "arrayvec", 686 | "bitflags", 687 | "cfg-if", 688 | "ryu", 689 | "static_assertions", 690 | ] 691 | 692 | [[package]] 693 | name = "libc" 694 | version = "0.2.126" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" 697 | 698 | [[package]] 699 | name = "linked-hash-map" 700 | version = "0.5.4" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" 703 | 704 | [[package]] 705 | name = "log" 706 | version = "0.4.17" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 709 | dependencies = [ 710 | "cfg-if", 711 | ] 712 | 713 | [[package]] 714 | name = "maplit" 715 | version = "1.0.2" 716 | source = "registry+https://github.com/rust-lang/crates.io-index" 717 | checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" 718 | 719 | [[package]] 720 | name = "matches" 721 | version = "0.1.9" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" 724 | 725 | [[package]] 726 | name = "memchr" 727 | version = "2.5.0" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 730 | 731 | [[package]] 732 | name = "mime" 733 | version = "0.3.16" 734 | source = "registry+https://github.com/rust-lang/crates.io-index" 735 | checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" 736 | 737 | [[package]] 738 | name = "mio" 739 | version = "0.8.3" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" 742 | dependencies = [ 743 | "libc", 744 | "log", 745 | "wasi", 746 | "windows-sys", 747 | ] 748 | 749 | [[package]] 750 | name = "native-tls" 751 | version = "0.2.10" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" 754 | dependencies = [ 755 | "lazy_static", 756 | "libc", 757 | "log", 758 | "openssl", 759 | "openssl-probe", 760 | "openssl-sys", 761 | "schannel", 762 | "security-framework", 763 | "security-framework-sys", 764 | "tempfile", 765 | ] 766 | 767 | [[package]] 768 | name = "nom" 769 | version = "5.1.2" 770 | source = "registry+https://github.com/rust-lang/crates.io-index" 771 | checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" 772 | dependencies = [ 773 | "lexical-core", 774 | "memchr", 775 | "version_check", 776 | ] 777 | 778 | [[package]] 779 | name = "num_cpus" 780 | version = "1.13.1" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" 783 | dependencies = [ 784 | "hermit-abi", 785 | "libc", 786 | ] 787 | 788 | [[package]] 789 | name = "once_cell" 790 | version = "1.12.0" 791 | source = "registry+https://github.com/rust-lang/crates.io-index" 792 | checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" 793 | 794 | [[package]] 795 | name = "opaque-debug" 796 | version = "0.2.3" 797 | source = "registry+https://github.com/rust-lang/crates.io-index" 798 | checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" 799 | 800 | [[package]] 801 | name = "opaque-debug" 802 | version = "0.3.0" 803 | source = "registry+https://github.com/rust-lang/crates.io-index" 804 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 805 | 806 | [[package]] 807 | name = "openssl" 808 | version = "0.10.40" 809 | source = "registry+https://github.com/rust-lang/crates.io-index" 810 | checksum = "fb81a6430ac911acb25fe5ac8f1d2af1b4ea8a4fdfda0f1ee4292af2e2d8eb0e" 811 | dependencies = [ 812 | "bitflags", 813 | "cfg-if", 814 | "foreign-types", 815 | "libc", 816 | "once_cell", 817 | "openssl-macros", 818 | "openssl-sys", 819 | ] 820 | 821 | [[package]] 822 | name = "openssl-macros" 823 | version = "0.1.0" 824 | source = "registry+https://github.com/rust-lang/crates.io-index" 825 | checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" 826 | dependencies = [ 827 | "proc-macro2", 828 | "quote", 829 | "syn", 830 | ] 831 | 832 | [[package]] 833 | name = "openssl-probe" 834 | version = "0.1.5" 835 | source = "registry+https://github.com/rust-lang/crates.io-index" 836 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 837 | 838 | [[package]] 839 | name = "openssl-sys" 840 | version = "0.9.74" 841 | source = "registry+https://github.com/rust-lang/crates.io-index" 842 | checksum = "835363342df5fba8354c5b453325b110ffd54044e588c539cf2f20a8014e4cb1" 843 | dependencies = [ 844 | "autocfg", 845 | "cc", 846 | "libc", 847 | "pkg-config", 848 | "vcpkg", 849 | ] 850 | 851 | [[package]] 852 | name = "percent-encoding" 853 | version = "2.1.0" 854 | source = "registry+https://github.com/rust-lang/crates.io-index" 855 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 856 | 857 | [[package]] 858 | name = "pest" 859 | version = "2.1.3" 860 | source = "registry+https://github.com/rust-lang/crates.io-index" 861 | checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" 862 | dependencies = [ 863 | "ucd-trie", 864 | ] 865 | 866 | [[package]] 867 | name = "pest_consume" 868 | version = "1.1.1" 869 | source = "registry+https://github.com/rust-lang/crates.io-index" 870 | checksum = "dcb7c2ab7ca422b1f9b9e821c96667dc6675885c8a986cb379f7fac36b229085" 871 | dependencies = [ 872 | "pest", 873 | "pest_consume_macros", 874 | "pest_derive", 875 | ] 876 | 877 | [[package]] 878 | name = "pest_consume_macros" 879 | version = "1.1.0" 880 | source = "registry+https://github.com/rust-lang/crates.io-index" 881 | checksum = "9d8630a7a899cb344ec1c16ba0a6b24240029af34bdc0a21f84e411d7f793f29" 882 | dependencies = [ 883 | "proc-macro2", 884 | "quote", 885 | "syn", 886 | ] 887 | 888 | [[package]] 889 | name = "pest_derive" 890 | version = "2.1.0" 891 | source = "registry+https://github.com/rust-lang/crates.io-index" 892 | checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" 893 | dependencies = [ 894 | "pest", 895 | "pest_generator", 896 | ] 897 | 898 | [[package]] 899 | name = "pest_generator" 900 | version = "2.1.3" 901 | source = "registry+https://github.com/rust-lang/crates.io-index" 902 | checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" 903 | dependencies = [ 904 | "pest", 905 | "pest_meta", 906 | "proc-macro2", 907 | "quote", 908 | "syn", 909 | ] 910 | 911 | [[package]] 912 | name = "pest_meta" 913 | version = "2.1.3" 914 | source = "registry+https://github.com/rust-lang/crates.io-index" 915 | checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" 916 | dependencies = [ 917 | "maplit", 918 | "pest", 919 | "sha-1", 920 | ] 921 | 922 | [[package]] 923 | name = "pin-project-lite" 924 | version = "0.2.9" 925 | source = "registry+https://github.com/rust-lang/crates.io-index" 926 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 927 | 928 | [[package]] 929 | name = "pin-utils" 930 | version = "0.1.0" 931 | source = "registry+https://github.com/rust-lang/crates.io-index" 932 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 933 | 934 | [[package]] 935 | name = "pkg-config" 936 | version = "0.3.25" 937 | source = "registry+https://github.com/rust-lang/crates.io-index" 938 | checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" 939 | 940 | [[package]] 941 | name = "pretty" 942 | version = "0.5.2" 943 | source = "registry+https://github.com/rust-lang/crates.io-index" 944 | checksum = "f60c0d9f6fc88ecdd245d90c1920ff76a430ab34303fc778d33b1d0a4c3bf6d3" 945 | dependencies = [ 946 | "typed-arena", 947 | ] 948 | 949 | [[package]] 950 | name = "proc-macro-error" 951 | version = "1.0.4" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 954 | dependencies = [ 955 | "proc-macro-error-attr", 956 | "proc-macro2", 957 | "quote", 958 | "syn", 959 | "version_check", 960 | ] 961 | 962 | [[package]] 963 | name = "proc-macro-error-attr" 964 | version = "1.0.4" 965 | source = "registry+https://github.com/rust-lang/crates.io-index" 966 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 967 | dependencies = [ 968 | "proc-macro2", 969 | "quote", 970 | "version_check", 971 | ] 972 | 973 | [[package]] 974 | name = "proc-macro2" 975 | version = "1.0.39" 976 | source = "registry+https://github.com/rust-lang/crates.io-index" 977 | checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" 978 | dependencies = [ 979 | "unicode-ident", 980 | ] 981 | 982 | [[package]] 983 | name = "quote" 984 | version = "1.0.19" 985 | source = "registry+https://github.com/rust-lang/crates.io-index" 986 | checksum = "f53dc8cf16a769a6f677e09e7ff2cd4be1ea0f48754aac39520536962011de0d" 987 | dependencies = [ 988 | "proc-macro2", 989 | ] 990 | 991 | [[package]] 992 | name = "redox_syscall" 993 | version = "0.2.13" 994 | source = "registry+https://github.com/rust-lang/crates.io-index" 995 | checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" 996 | dependencies = [ 997 | "bitflags", 998 | ] 999 | 1000 | [[package]] 1001 | name = "remove_dir_all" 1002 | version = "0.5.3" 1003 | source = "registry+https://github.com/rust-lang/crates.io-index" 1004 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 1005 | dependencies = [ 1006 | "winapi", 1007 | ] 1008 | 1009 | [[package]] 1010 | name = "reqwest" 1011 | version = "0.11.10" 1012 | source = "registry+https://github.com/rust-lang/crates.io-index" 1013 | checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb" 1014 | dependencies = [ 1015 | "base64", 1016 | "bytes", 1017 | "encoding_rs", 1018 | "futures-core", 1019 | "futures-util", 1020 | "h2", 1021 | "http", 1022 | "http-body", 1023 | "hyper", 1024 | "hyper-tls", 1025 | "ipnet", 1026 | "js-sys", 1027 | "lazy_static", 1028 | "log", 1029 | "mime", 1030 | "native-tls", 1031 | "percent-encoding", 1032 | "pin-project-lite", 1033 | "serde", 1034 | "serde_json", 1035 | "serde_urlencoded", 1036 | "tokio", 1037 | "tokio-native-tls", 1038 | "url", 1039 | "wasm-bindgen", 1040 | "wasm-bindgen-futures", 1041 | "web-sys", 1042 | "winreg", 1043 | ] 1044 | 1045 | [[package]] 1046 | name = "rustc_version" 1047 | version = "0.4.0" 1048 | source = "registry+https://github.com/rust-lang/crates.io-index" 1049 | checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" 1050 | dependencies = [ 1051 | "semver", 1052 | ] 1053 | 1054 | [[package]] 1055 | name = "ryu" 1056 | version = "1.0.10" 1057 | source = "registry+https://github.com/rust-lang/crates.io-index" 1058 | checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" 1059 | 1060 | [[package]] 1061 | name = "schannel" 1062 | version = "0.1.20" 1063 | source = "registry+https://github.com/rust-lang/crates.io-index" 1064 | checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" 1065 | dependencies = [ 1066 | "lazy_static", 1067 | "windows-sys", 1068 | ] 1069 | 1070 | [[package]] 1071 | name = "security-framework" 1072 | version = "2.6.1" 1073 | source = "registry+https://github.com/rust-lang/crates.io-index" 1074 | checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" 1075 | dependencies = [ 1076 | "bitflags", 1077 | "core-foundation", 1078 | "core-foundation-sys", 1079 | "libc", 1080 | "security-framework-sys", 1081 | ] 1082 | 1083 | [[package]] 1084 | name = "security-framework-sys" 1085 | version = "2.6.1" 1086 | source = "registry+https://github.com/rust-lang/crates.io-index" 1087 | checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" 1088 | dependencies = [ 1089 | "core-foundation-sys", 1090 | "libc", 1091 | ] 1092 | 1093 | [[package]] 1094 | name = "semver" 1095 | version = "1.0.9" 1096 | source = "registry+https://github.com/rust-lang/crates.io-index" 1097 | checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd" 1098 | 1099 | [[package]] 1100 | name = "serde" 1101 | version = "1.0.137" 1102 | source = "registry+https://github.com/rust-lang/crates.io-index" 1103 | checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" 1104 | dependencies = [ 1105 | "serde_derive", 1106 | ] 1107 | 1108 | [[package]] 1109 | name = "serde_cbor" 1110 | version = "0.11.2" 1111 | source = "registry+https://github.com/rust-lang/crates.io-index" 1112 | checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" 1113 | dependencies = [ 1114 | "half", 1115 | "serde", 1116 | ] 1117 | 1118 | [[package]] 1119 | name = "serde_derive" 1120 | version = "1.0.137" 1121 | source = "registry+https://github.com/rust-lang/crates.io-index" 1122 | checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" 1123 | dependencies = [ 1124 | "proc-macro2", 1125 | "quote", 1126 | "syn", 1127 | ] 1128 | 1129 | [[package]] 1130 | name = "serde_dhall" 1131 | version = "0.11.1" 1132 | source = "registry+https://github.com/rust-lang/crates.io-index" 1133 | checksum = "0f7791cbb53e9db687bc88935eee148bacb2c6b2fc56418a2f20d54e7a74ea47" 1134 | dependencies = [ 1135 | "dhall", 1136 | "dhall_proc_macros", 1137 | "doc-comment", 1138 | "serde", 1139 | "url", 1140 | ] 1141 | 1142 | [[package]] 1143 | name = "serde_json" 1144 | version = "1.0.81" 1145 | source = "registry+https://github.com/rust-lang/crates.io-index" 1146 | checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" 1147 | dependencies = [ 1148 | "itoa", 1149 | "ryu", 1150 | "serde", 1151 | ] 1152 | 1153 | [[package]] 1154 | name = "serde_urlencoded" 1155 | version = "0.7.1" 1156 | source = "registry+https://github.com/rust-lang/crates.io-index" 1157 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1158 | dependencies = [ 1159 | "form_urlencoded", 1160 | "itoa", 1161 | "ryu", 1162 | "serde", 1163 | ] 1164 | 1165 | [[package]] 1166 | name = "serde_yaml" 1167 | version = "0.8.24" 1168 | source = "registry+https://github.com/rust-lang/crates.io-index" 1169 | checksum = "707d15895415db6628332b737c838b88c598522e4dc70647e59b72312924aebc" 1170 | dependencies = [ 1171 | "indexmap", 1172 | "ryu", 1173 | "serde", 1174 | "yaml-rust", 1175 | ] 1176 | 1177 | [[package]] 1178 | name = "sha-1" 1179 | version = "0.8.2" 1180 | source = "registry+https://github.com/rust-lang/crates.io-index" 1181 | checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" 1182 | dependencies = [ 1183 | "block-buffer 0.7.3", 1184 | "digest 0.8.1", 1185 | "fake-simd", 1186 | "opaque-debug 0.2.3", 1187 | ] 1188 | 1189 | [[package]] 1190 | name = "sha2" 1191 | version = "0.9.9" 1192 | source = "registry+https://github.com/rust-lang/crates.io-index" 1193 | checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" 1194 | dependencies = [ 1195 | "block-buffer 0.9.0", 1196 | "cfg-if", 1197 | "cpufeatures", 1198 | "digest 0.9.0", 1199 | "opaque-debug 0.3.0", 1200 | ] 1201 | 1202 | [[package]] 1203 | name = "similar" 1204 | version = "2.1.0" 1205 | source = "registry+https://github.com/rust-lang/crates.io-index" 1206 | checksum = "2e24979f63a11545f5f2c60141afe249d4f19f84581ea2138065e400941d83d3" 1207 | 1208 | [[package]] 1209 | name = "slab" 1210 | version = "0.4.6" 1211 | source = "registry+https://github.com/rust-lang/crates.io-index" 1212 | checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" 1213 | 1214 | [[package]] 1215 | name = "socket2" 1216 | version = "0.4.4" 1217 | source = "registry+https://github.com/rust-lang/crates.io-index" 1218 | checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" 1219 | dependencies = [ 1220 | "libc", 1221 | "winapi", 1222 | ] 1223 | 1224 | [[package]] 1225 | name = "stable_deref_trait" 1226 | version = "1.2.0" 1227 | source = "registry+https://github.com/rust-lang/crates.io-index" 1228 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1229 | 1230 | [[package]] 1231 | name = "static_assertions" 1232 | version = "1.1.0" 1233 | source = "registry+https://github.com/rust-lang/crates.io-index" 1234 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 1235 | 1236 | [[package]] 1237 | name = "str-macro" 1238 | version = "1.0.0" 1239 | source = "registry+https://github.com/rust-lang/crates.io-index" 1240 | checksum = "9b7514866270741c7b03dd36e2c68f71cf064b230e8217c81bbd4d619d564864" 1241 | 1242 | [[package]] 1243 | name = "syn" 1244 | version = "1.0.98" 1245 | source = "registry+https://github.com/rust-lang/crates.io-index" 1246 | checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" 1247 | dependencies = [ 1248 | "proc-macro2", 1249 | "quote", 1250 | "unicode-ident", 1251 | ] 1252 | 1253 | [[package]] 1254 | name = "tempfile" 1255 | version = "3.3.0" 1256 | source = "registry+https://github.com/rust-lang/crates.io-index" 1257 | checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" 1258 | dependencies = [ 1259 | "cfg-if", 1260 | "fastrand", 1261 | "libc", 1262 | "redox_syscall", 1263 | "remove_dir_all", 1264 | "winapi", 1265 | ] 1266 | 1267 | [[package]] 1268 | name = "terminal_size" 1269 | version = "0.1.17" 1270 | source = "registry+https://github.com/rust-lang/crates.io-index" 1271 | checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" 1272 | dependencies = [ 1273 | "libc", 1274 | "winapi", 1275 | ] 1276 | 1277 | [[package]] 1278 | name = "thiserror" 1279 | version = "1.0.31" 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" 1281 | checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" 1282 | dependencies = [ 1283 | "thiserror-impl", 1284 | ] 1285 | 1286 | [[package]] 1287 | name = "thiserror-impl" 1288 | version = "1.0.31" 1289 | source = "registry+https://github.com/rust-lang/crates.io-index" 1290 | checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" 1291 | dependencies = [ 1292 | "proc-macro2", 1293 | "quote", 1294 | "syn", 1295 | ] 1296 | 1297 | [[package]] 1298 | name = "tinyvec" 1299 | version = "1.6.0" 1300 | source = "registry+https://github.com/rust-lang/crates.io-index" 1301 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 1302 | dependencies = [ 1303 | "tinyvec_macros", 1304 | ] 1305 | 1306 | [[package]] 1307 | name = "tinyvec_macros" 1308 | version = "0.1.0" 1309 | source = "registry+https://github.com/rust-lang/crates.io-index" 1310 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 1311 | 1312 | [[package]] 1313 | name = "tokio" 1314 | version = "1.19.1" 1315 | source = "registry+https://github.com/rust-lang/crates.io-index" 1316 | checksum = "95eec79ea28c00a365f539f1961e9278fbcaf81c0ff6aaf0e93c181352446948" 1317 | dependencies = [ 1318 | "bytes", 1319 | "libc", 1320 | "memchr", 1321 | "mio", 1322 | "num_cpus", 1323 | "once_cell", 1324 | "pin-project-lite", 1325 | "socket2", 1326 | "winapi", 1327 | ] 1328 | 1329 | [[package]] 1330 | name = "tokio-native-tls" 1331 | version = "0.3.0" 1332 | source = "registry+https://github.com/rust-lang/crates.io-index" 1333 | checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" 1334 | dependencies = [ 1335 | "native-tls", 1336 | "tokio", 1337 | ] 1338 | 1339 | [[package]] 1340 | name = "tokio-util" 1341 | version = "0.7.3" 1342 | source = "registry+https://github.com/rust-lang/crates.io-index" 1343 | checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" 1344 | dependencies = [ 1345 | "bytes", 1346 | "futures-core", 1347 | "futures-sink", 1348 | "pin-project-lite", 1349 | "tokio", 1350 | "tracing", 1351 | ] 1352 | 1353 | [[package]] 1354 | name = "tower-service" 1355 | version = "0.3.1" 1356 | source = "registry+https://github.com/rust-lang/crates.io-index" 1357 | checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" 1358 | 1359 | [[package]] 1360 | name = "tracing" 1361 | version = "0.1.34" 1362 | source = "registry+https://github.com/rust-lang/crates.io-index" 1363 | checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" 1364 | dependencies = [ 1365 | "cfg-if", 1366 | "pin-project-lite", 1367 | "tracing-core", 1368 | ] 1369 | 1370 | [[package]] 1371 | name = "tracing-core" 1372 | version = "0.1.26" 1373 | source = "registry+https://github.com/rust-lang/crates.io-index" 1374 | checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" 1375 | dependencies = [ 1376 | "lazy_static", 1377 | ] 1378 | 1379 | [[package]] 1380 | name = "try-lock" 1381 | version = "0.2.3" 1382 | source = "registry+https://github.com/rust-lang/crates.io-index" 1383 | checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" 1384 | 1385 | [[package]] 1386 | name = "typed-arena" 1387 | version = "1.7.0" 1388 | source = "registry+https://github.com/rust-lang/crates.io-index" 1389 | checksum = "a9b2228007eba4120145f785df0f6c92ea538f5a3635a612ecf4e334c8c1446d" 1390 | 1391 | [[package]] 1392 | name = "typenum" 1393 | version = "1.15.0" 1394 | source = "registry+https://github.com/rust-lang/crates.io-index" 1395 | checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" 1396 | 1397 | [[package]] 1398 | name = "ucd-trie" 1399 | version = "0.1.3" 1400 | source = "registry+https://github.com/rust-lang/crates.io-index" 1401 | checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" 1402 | 1403 | [[package]] 1404 | name = "unicode-bidi" 1405 | version = "0.3.8" 1406 | source = "registry+https://github.com/rust-lang/crates.io-index" 1407 | checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" 1408 | 1409 | [[package]] 1410 | name = "unicode-ident" 1411 | version = "1.0.0" 1412 | source = "registry+https://github.com/rust-lang/crates.io-index" 1413 | checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" 1414 | 1415 | [[package]] 1416 | name = "unicode-normalization" 1417 | version = "0.1.19" 1418 | source = "registry+https://github.com/rust-lang/crates.io-index" 1419 | checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" 1420 | dependencies = [ 1421 | "tinyvec", 1422 | ] 1423 | 1424 | [[package]] 1425 | name = "unicode-segmentation" 1426 | version = "1.9.0" 1427 | source = "registry+https://github.com/rust-lang/crates.io-index" 1428 | checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" 1429 | 1430 | [[package]] 1431 | name = "unicode-width" 1432 | version = "0.1.9" 1433 | source = "registry+https://github.com/rust-lang/crates.io-index" 1434 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 1435 | 1436 | [[package]] 1437 | name = "url" 1438 | version = "2.2.2" 1439 | source = "registry+https://github.com/rust-lang/crates.io-index" 1440 | checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" 1441 | dependencies = [ 1442 | "form_urlencoded", 1443 | "idna", 1444 | "matches", 1445 | "percent-encoding", 1446 | ] 1447 | 1448 | [[package]] 1449 | name = "vcpkg" 1450 | version = "0.2.15" 1451 | source = "registry+https://github.com/rust-lang/crates.io-index" 1452 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1453 | 1454 | [[package]] 1455 | name = "version_check" 1456 | version = "0.9.4" 1457 | source = "registry+https://github.com/rust-lang/crates.io-index" 1458 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1459 | 1460 | [[package]] 1461 | name = "want" 1462 | version = "0.3.0" 1463 | source = "registry+https://github.com/rust-lang/crates.io-index" 1464 | checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 1465 | dependencies = [ 1466 | "log", 1467 | "try-lock", 1468 | ] 1469 | 1470 | [[package]] 1471 | name = "wasi" 1472 | version = "0.11.0+wasi-snapshot-preview1" 1473 | source = "registry+https://github.com/rust-lang/crates.io-index" 1474 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1475 | 1476 | [[package]] 1477 | name = "wasm-bindgen" 1478 | version = "0.2.80" 1479 | source = "registry+https://github.com/rust-lang/crates.io-index" 1480 | checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" 1481 | dependencies = [ 1482 | "cfg-if", 1483 | "wasm-bindgen-macro", 1484 | ] 1485 | 1486 | [[package]] 1487 | name = "wasm-bindgen-backend" 1488 | version = "0.2.80" 1489 | source = "registry+https://github.com/rust-lang/crates.io-index" 1490 | checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" 1491 | dependencies = [ 1492 | "bumpalo", 1493 | "lazy_static", 1494 | "log", 1495 | "proc-macro2", 1496 | "quote", 1497 | "syn", 1498 | "wasm-bindgen-shared", 1499 | ] 1500 | 1501 | [[package]] 1502 | name = "wasm-bindgen-futures" 1503 | version = "0.4.30" 1504 | source = "registry+https://github.com/rust-lang/crates.io-index" 1505 | checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2" 1506 | dependencies = [ 1507 | "cfg-if", 1508 | "js-sys", 1509 | "wasm-bindgen", 1510 | "web-sys", 1511 | ] 1512 | 1513 | [[package]] 1514 | name = "wasm-bindgen-macro" 1515 | version = "0.2.80" 1516 | source = "registry+https://github.com/rust-lang/crates.io-index" 1517 | checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" 1518 | dependencies = [ 1519 | "quote", 1520 | "wasm-bindgen-macro-support", 1521 | ] 1522 | 1523 | [[package]] 1524 | name = "wasm-bindgen-macro-support" 1525 | version = "0.2.80" 1526 | source = "registry+https://github.com/rust-lang/crates.io-index" 1527 | checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" 1528 | dependencies = [ 1529 | "proc-macro2", 1530 | "quote", 1531 | "syn", 1532 | "wasm-bindgen-backend", 1533 | "wasm-bindgen-shared", 1534 | ] 1535 | 1536 | [[package]] 1537 | name = "wasm-bindgen-shared" 1538 | version = "0.2.80" 1539 | source = "registry+https://github.com/rust-lang/crates.io-index" 1540 | checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" 1541 | 1542 | [[package]] 1543 | name = "web-sys" 1544 | version = "0.3.57" 1545 | source = "registry+https://github.com/rust-lang/crates.io-index" 1546 | checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" 1547 | dependencies = [ 1548 | "js-sys", 1549 | "wasm-bindgen", 1550 | ] 1551 | 1552 | [[package]] 1553 | name = "winapi" 1554 | version = "0.3.9" 1555 | source = "registry+https://github.com/rust-lang/crates.io-index" 1556 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1557 | dependencies = [ 1558 | "winapi-i686-pc-windows-gnu", 1559 | "winapi-x86_64-pc-windows-gnu", 1560 | ] 1561 | 1562 | [[package]] 1563 | name = "winapi-i686-pc-windows-gnu" 1564 | version = "0.4.0" 1565 | source = "registry+https://github.com/rust-lang/crates.io-index" 1566 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1567 | 1568 | [[package]] 1569 | name = "winapi-x86_64-pc-windows-gnu" 1570 | version = "0.4.0" 1571 | source = "registry+https://github.com/rust-lang/crates.io-index" 1572 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1573 | 1574 | [[package]] 1575 | name = "windows-sys" 1576 | version = "0.36.1" 1577 | source = "registry+https://github.com/rust-lang/crates.io-index" 1578 | checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" 1579 | dependencies = [ 1580 | "windows_aarch64_msvc", 1581 | "windows_i686_gnu", 1582 | "windows_i686_msvc", 1583 | "windows_x86_64_gnu", 1584 | "windows_x86_64_msvc", 1585 | ] 1586 | 1587 | [[package]] 1588 | name = "windows_aarch64_msvc" 1589 | version = "0.36.1" 1590 | source = "registry+https://github.com/rust-lang/crates.io-index" 1591 | checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" 1592 | 1593 | [[package]] 1594 | name = "windows_i686_gnu" 1595 | version = "0.36.1" 1596 | source = "registry+https://github.com/rust-lang/crates.io-index" 1597 | checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" 1598 | 1599 | [[package]] 1600 | name = "windows_i686_msvc" 1601 | version = "0.36.1" 1602 | source = "registry+https://github.com/rust-lang/crates.io-index" 1603 | checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" 1604 | 1605 | [[package]] 1606 | name = "windows_x86_64_gnu" 1607 | version = "0.36.1" 1608 | source = "registry+https://github.com/rust-lang/crates.io-index" 1609 | checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" 1610 | 1611 | [[package]] 1612 | name = "windows_x86_64_msvc" 1613 | version = "0.36.1" 1614 | source = "registry+https://github.com/rust-lang/crates.io-index" 1615 | checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" 1616 | 1617 | [[package]] 1618 | name = "winreg" 1619 | version = "0.10.1" 1620 | source = "registry+https://github.com/rust-lang/crates.io-index" 1621 | checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" 1622 | dependencies = [ 1623 | "winapi", 1624 | ] 1625 | 1626 | [[package]] 1627 | name = "yaml-rust" 1628 | version = "0.4.5" 1629 | source = "registry+https://github.com/rust-lang/crates.io-index" 1630 | checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" 1631 | dependencies = [ 1632 | "linked-hash-map", 1633 | ] 1634 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "dhall", 4 | "macro", 5 | "example", 6 | "example/opts", 7 | ] 8 | default-members = [ "macro" ] 9 | 10 | [package] 11 | name = "cli-compose" 12 | version = "0.1.0-alpha.0" 13 | description = "Composable, strict CLI framework with static analysis for Rust" 14 | authors = ["Kodai Matsumoto "] 15 | repository = "https://github.com/0918nobita/cli-compose" 16 | categories = ["command-line-interface"] 17 | keywords = [ 18 | "argument", 19 | "cli", 20 | "arg", 21 | "parser", 22 | "parse", 23 | ] 24 | edition = "2021" 25 | license = "MIT" 26 | readme = "README.md" 27 | include = [ 28 | "src/**/*", 29 | "Cargo.toml", 30 | "Cargo.lock", 31 | "LICENSE", 32 | "README.md", 33 | ] 34 | 35 | [features] 36 | codegen = [] 37 | runtime = [] 38 | schema = [] 39 | 40 | [dependencies] 41 | anyhow = "1.0.58" 42 | cli-compose-macro = { path = "./macro" } 43 | convert_case = "0.5.0" 44 | derive_more = "0.99.17" 45 | proc-macro2 = "1.0.39" 46 | quote = "1.0.19" 47 | str-macro = "1.0.0" 48 | thiserror = "1.0.31" 49 | 50 | [dependencies.syn] 51 | version = "1.0.98" 52 | features = ["full", "extra-traits"] 53 | 54 | [dev-dependencies] 55 | insta = "1.15.0" 56 | 57 | [profile.release] 58 | lto = true 59 | codegen-units = 1 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Kodai Matsumoto 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cli-compose 2 | 3 | > Composable, strict CLI framework with static analysis for Rust 4 | 5 | [![Test](https://github.com/0918nobita/cli-compose/actions/workflows/test.yml/badge.svg)](https://github.com/0918nobita/cli-compose/actions/workflows/test.yml) [![Clippy](https://github.com/0918nobita/cli-compose/actions/workflows/clippy.yml/badge.svg)](https://github.com/0918nobita/cli-compose/actions/workflows/clippy.yml) [![Rustfmt](https://github.com/0918nobita/cli-compose/actions/workflows/rustfmt.yml/badge.svg)](https://github.com/0918nobita/cli-compose/actions/workflows/rustfmt.yml) [![codecov](https://codecov.io/gh/0918nobita/cli-compose/branch/main/graph/badge.svg?token=PBAO6WHOKE)](https://codecov.io/gh/0918nobita/cli-compose) 6 | 7 | Please note that this is still at an early stage of development. Hence this may contain bugs, unimplemented features, or unstable features. 8 | 9 | ## Implementation goals 10 | 11 | ### Directory structure 12 | 13 | ```text 14 | project 15 | ├ opts 16 | │ ├ src 17 | │ │ └ lib.rs 18 | │ └ Cargo.toml 19 | ├ src 20 | │ └ main.rs 21 | ├ build.rs 22 | └ Cargo.toml 23 | ``` 24 | 25 | ### `Cargo.toml` 26 | 27 | ```toml 28 | [workspace] 29 | members = [ "opts" ] 30 | 31 | [package] 32 | name = "example-cli" 33 | edition = "2021" 34 | 35 | [dependencies] 36 | opts = { path = "./opts" } 37 | 38 | [dependencies.cli-compose] 39 | git = "https://github.com/0918nobita/cli-compose" 40 | package = "cli-compose" 41 | features = [ "runtime" ] 42 | 43 | [build-dependencies.cli-compose] 44 | git = "https://github.com/0918nobita/cli-compose" 45 | package = "cli-compose" 46 | features = [ "codegen" ] 47 | ``` 48 | 49 | ### `opts/Cargo.toml` 50 | 51 | ```toml 52 | [package] 53 | name = "opts" 54 | edition = "2021" 55 | 56 | [dependencies.cli-compose] 57 | git = "https://github.com/0918nobita/cli-compose" 58 | package = "cli-compose" 59 | features = [ "schema" ] 60 | ``` 61 | 62 | ### `opts/src/lib.rs` 63 | 64 | ```rust 65 | use cli_compose::schema::{ArgOpt, FromKebabStr, SingleSelect, Opt, PosArg}; 66 | 67 | // All derivers treat doc comments as help messages. 68 | 69 | /// Source file path 70 | #[derive(Debug, PosArg)] 71 | pub struct Input(String); 72 | 73 | /// Reads source code from stdin 74 | #[derive(Debug, Opt)] 75 | #[opt(long = "stdin")] // overrides its long name 76 | pub struct StdinOpt; 77 | 78 | /// Settings related to input 79 | #[derive(SingleSelect)] 80 | pub enum InputGroup { 81 | Input(Input), 82 | StdinOpt(StdinOpt), 83 | } 84 | 85 | /// Source file format 86 | #[derive(Debug, ArgOpt, FromKebabStr)] 87 | #[arg_opt(use_default)] 88 | pub enum InputFormat { 89 | Json, 90 | Yaml, 91 | } 92 | 93 | impl Default for InputFormat { 94 | fn default() -> Self { 95 | InputFormat::Json 96 | } 97 | } 98 | 99 | /// Output file path 100 | #[derive(Debug, ArgOpt)] 101 | #[arg_opt(short = 'o')] // configures its short name 102 | pub struct Output(String); 103 | 104 | /// Outputs to stdout 105 | #[derive(Debug, Opt)] 106 | #[opt(long = "stdout")] 107 | pub struct StdoutOpt; 108 | 109 | /// Settings related to output 110 | #[derive(SingleSelect)] 111 | pub enum OutputGroup { 112 | Output(Output), 113 | StdoutOpt(StdoutOpt), 114 | } 115 | 116 | #[derive(Opt)] 117 | pub struct Verbose; 118 | 119 | #[derive(Cli)] 120 | #[cli( 121 | name = "example", 122 | version = from_crate, 123 | desc = from_crate 124 | )] 125 | pub struct ExampleCli; 126 | ``` 127 | 128 | ### `build.rs` 129 | 130 | ```rust 131 | use cli_compose::codegen::define_cli; 132 | use opts::{Cli, InputFormat, InputGroup, OutputGroup, Verbose}; 133 | 134 | fn main() { 135 | define_cli::("opts") 136 | .unwrap() 137 | .member::() 138 | .member::() 139 | .member::() 140 | .member::() 141 | .build("ExampleCliResult") 142 | .unwrap(); 143 | } 144 | } 145 | ``` 146 | 147 | ### `src/main.rs` 148 | 149 | ```rust 150 | use cli_compose::runtime::{use_cli, AsCli}; 151 | 152 | // includes `$OUT_DIR/cli_compose/example_cli.rs` 153 | // and imports `ExampleCli` struct 154 | use_cli! { example_cli::ExampleCli } 155 | 156 | pub fn main() { 157 | let res = Cli::parse(std::env::args()); // res: ExampleCliResult 158 | 159 | println!("Input: {:?}", res.input); 160 | println!("InputFormat: {:?}", res.input_format); 161 | println!("Output: {:?}", res.output); 162 | println!("Verbose: {:?}", res.verbose); 163 | } 164 | ``` 165 | 166 | ### Results 167 | 168 | `$ cargo run -- --verbose --stdin --stdout`: 169 | 170 | ```text 171 | Input: Stdin(StdinOpt) 172 | InputFormat: Json 173 | Output: Stdout(StdoutOpt) 174 | Verbose: Some(Verbose) 175 | ``` 176 | 177 | `$ cargo run -- --input-format yaml -o output.txt input.yaml`: 178 | 179 | ```text 180 | Input: File("input.txt") 181 | InputFormat: Yaml 182 | Output: File("output.txt") 183 | Verbose: None 184 | ``` 185 | 186 | ## Example program to test features already implemented 187 | 188 | ```bash 189 | cargo run -p example 190 | ``` 191 | -------------------------------------------------------------------------------- /dhall/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cli-compose-dhall" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | serde = { version = "1.0", features = ["derive"] } 8 | serde_dhall = "0.11.1" 9 | -------------------------------------------------------------------------------- /dhall/cli.dhall: -------------------------------------------------------------------------------- 1 | let PosArg = 2 | { Type = 3 | { rust_type: Text 4 | , possible_values: Optional (List Text) 5 | , default_value: Optional Text 6 | } 7 | , default = 8 | { possible_values = None (List Text) 9 | , default_value = None Text 10 | } 11 | } 12 | 13 | let Flag = 14 | < Both: { long: Text, short: Text } 15 | | LongOnly: Text 16 | | ShortOnly: Text 17 | > 18 | 19 | let Opt = 20 | { rust_type: Text 21 | , flag: Flag 22 | } 23 | 24 | let ArgOpt = 25 | { rust_type: Text 26 | , flag: Flag 27 | } 28 | 29 | let Member = 30 | < PosArg: PosArg.Type 31 | | Opt: Opt 32 | | ArgOpt: ArgOpt 33 | > 34 | 35 | let sourceFileArg = PosArg::{ rust_type = "SourceFile" } 36 | 37 | let outfileOpt: ArgOpt = { 38 | rust_type = "Outfile", -- generated Rust type 39 | flag = Flag.Both { long = "outfile", short = "o" } 40 | } 41 | 42 | in 43 | 44 | [Member.PosArg sourceFileArg, Member.ArgOpt outfileOpt]: List Member 45 | -------------------------------------------------------------------------------- /dhall/color.dhall: -------------------------------------------------------------------------------- 1 | let Color = 2 | < Hsv: { hue: Double, sat: Double, val: Double } 3 | | Rgb: { red: Double, green: Double, blue: Double } 4 | > 5 | in 6 | Color.Rgb { red = 1.0, green = 1.0, blue = 0.0 } 7 | -------------------------------------------------------------------------------- /dhall/point.dhall: -------------------------------------------------------------------------------- 1 | { x = 1, y = 3 + 4 } : { x: Natural, y: Natural } 2 | -------------------------------------------------------------------------------- /dhall/src/main.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | 3 | #[allow(dead_code)] 4 | #[derive(Debug, Deserialize)] 5 | struct Point { 6 | x: u64, 7 | y: u64, 8 | } 9 | 10 | #[allow(dead_code)] 11 | #[derive(Debug, Deserialize)] 12 | enum Color { 13 | Hsv { hue: f64, sat: f64, val: f64 }, 14 | Rgb { red: f64, green: f64, blue: f64 }, 15 | } 16 | 17 | fn main() { 18 | let point = serde_dhall::from_file("point.dhall") 19 | .parse::() 20 | .unwrap(); 21 | println!("{:?}", point); 22 | 23 | let color = serde_dhall::from_file("color.dhall") 24 | .parse::() 25 | .unwrap(); 26 | println!("{:?}", color); 27 | } 28 | -------------------------------------------------------------------------------- /docs/00-install.md: -------------------------------------------------------------------------------- 1 | # cli-compose のインストール方法 2 | 3 | まだ初回リリースをしていないため、git リモートリポジトリを参照する方法でのみインストールできます。 4 | 5 | `Cargo.toml` で以下のように追記してください。 6 | 7 | ```toml 8 | [dependencies.cli-compose] 9 | git = "https://github.com/0918nobita/cli-compose" 10 | package = "cli-compose" 11 | ``` 12 | -------------------------------------------------------------------------------- /docs/01-syntax.md: -------------------------------------------------------------------------------- 1 | # 文法について 2 | 3 | WIP 4 | -------------------------------------------------------------------------------- /docs/02-pos-arg.md: -------------------------------------------------------------------------------- 1 | # Positional argument (位置指定引数) 2 | 3 | オプショナルに設定した場合、実行時の挙動の曖昧さを排除するために、以下のような制限を受けます。 4 | 5 | - すべての必須な位置指定引数よりも後ろに配置されなければなりません。 6 | - オプショナルな位置指定引数が連続する場合、実行時には前に配置されたものから優先的に実引数が当てはめられます。 7 | 8 | WIP 9 | -------------------------------------------------------------------------------- /docs/03-arg-opt.md: -------------------------------------------------------------------------------- 1 | # Option with argument (引数付きオプション) 2 | 3 | より詳細には以下の2種類があります。 4 | 5 | - ``--[英数字列]`` :引数付き複数文字オプション 6 | - `-[英数字1文字]` :引数付き短縮オプション 7 | 8 | WIP 9 | -------------------------------------------------------------------------------- /docs/04-opt.md: -------------------------------------------------------------------------------- 1 | # Option without argument (引数なしオプション) 2 | 3 | より詳細には以下の2種類があります。 4 | 5 | - ``--[英数字列]`` :引数なし複数文字オプション 6 | - `-[英数字1文字]` :引数なし短縮オプション 7 | 8 | WIP 9 | -------------------------------------------------------------------------------- /example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example" 3 | version = "0.1.0-alpha.0" 4 | authors = ["Kodai Matsumoto "] 5 | license = "MIT" 6 | edition = "2021" 7 | 8 | [dependencies] 9 | cli-compose = { path = "../", features = [ "runtime" ] } 10 | example-opts = { path = "./opts" } 11 | 12 | [build-dependencies] 13 | cli-compose = { path = "../", features = [ "codegen" ] } 14 | example-opts = { path = "./opts" } 15 | -------------------------------------------------------------------------------- /example/build.rs: -------------------------------------------------------------------------------- 1 | use example_opts::*; 2 | 3 | fn main() { 4 | cli_compose::codegen::define_cli::("example_opts") 5 | .unwrap() 6 | .member::() 7 | .member::() 8 | .member::() 9 | .member::() 10 | .member::() 11 | .member::() 12 | .build("CliResult") 13 | .unwrap(); 14 | 15 | println!("cargo:rerun-if-changed=build.rs"); 16 | } 17 | -------------------------------------------------------------------------------- /example/opts/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-opts" 3 | version = "0.1.0-alpha.0" 4 | authors = ["Kodai Matsumoto "] 5 | license = "MIT" 6 | edition = "2021" 7 | 8 | [dependencies] 9 | cli-compose = { path = "../../", features = [ "schema" ] } 10 | -------------------------------------------------------------------------------- /example/opts/src/lib.rs: -------------------------------------------------------------------------------- 1 | use cli_compose::schema::{ArgOpt, Cli, FromKebabStr, Opt, PosArg}; 2 | 3 | /// 入力ファイルのパス 4 | #[derive(Debug, PosArg)] 5 | pub struct Input(String); 6 | 7 | /// 標準入力から読み取る 8 | #[derive(Debug, Opt)] 9 | #[opt(long = "stdin")] 10 | pub struct StdinOpt; 11 | 12 | /// 入力ファイルの形式 13 | #[derive(Debug, ArgOpt, FromKebabStr)] 14 | #[arg_opt(use_default)] 15 | pub enum InputFormat { 16 | Json, 17 | Yaml, 18 | } 19 | 20 | /// 出力ファイルのパス 21 | #[derive(Debug, ArgOpt)] 22 | #[arg_opt(short = 'o')] 23 | pub struct Output(String); 24 | 25 | /// 標準出力に出力する 26 | #[derive(Debug, Opt)] 27 | #[opt(long = "stdout")] 28 | pub struct StdoutOpt; 29 | 30 | /// 詳細を標準エラーに出力する 31 | #[derive(Debug, Opt)] 32 | #[opt(short = 'V')] 33 | pub struct Verbose; 34 | 35 | /// CLI ツールの説明文 36 | #[derive(Cli)] 37 | #[cli(name = "example", version = from_crate)] 38 | pub struct Cli; 39 | -------------------------------------------------------------------------------- /example/src/main.rs: -------------------------------------------------------------------------------- 1 | use cli_compose::runtime::{use_cli, AsCli}; 2 | 3 | use_cli! { example_opts::Cli } 4 | 5 | fn main() { 6 | let _res = Cli::parse(std::env::args()); 7 | } 8 | -------------------------------------------------------------------------------- /macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cli-compose-macro" 3 | description = "Macro implementations of cli-compose" 4 | version = "0.1.0-alpha.0" 5 | authors = ["Kodai Matsumoto "] 6 | repository = "https://github.com/0918nobita/cli-compose" 7 | license = "MIT" 8 | edition = "2021" 9 | 10 | [lib] 11 | proc-macro = true 12 | 13 | [dependencies] 14 | anyhow = "1.0.58" 15 | bae = "0.1.7" 16 | convert_case = "0.5.0" 17 | derive_more = "0.99.17" 18 | proc-macro2 = "1.0.39" 19 | quote = "1.0.19" 20 | 21 | [dependencies.syn] 22 | version = "1.0.98" 23 | features = ["full", "extra-traits"] 24 | 25 | [dev-dependencies] 26 | insta = "1.15.0" 27 | -------------------------------------------------------------------------------- /macro/src/deriver.rs: -------------------------------------------------------------------------------- 1 | mod arg_opt; 2 | mod cli; 3 | mod from_kebab_str; 4 | mod multi_select; 5 | mod opt; 6 | mod pos_arg; 7 | mod single_select; 8 | 9 | pub use arg_opt::derive_arg_opt; 10 | pub use cli::derive_cli; 11 | pub use from_kebab_str::derive_from_kebab_str; 12 | pub use multi_select::derive_multi_select; 13 | pub use opt::derive_opt; 14 | pub use pos_arg::derive_pos_arg; 15 | pub use single_select::derive_single_select; 16 | -------------------------------------------------------------------------------- /macro/src/deriver/arg_opt.rs: -------------------------------------------------------------------------------- 1 | use bae::FromAttributes; 2 | use convert_case::{Case, Casing}; 3 | use proc_macro2::TokenStream; 4 | use quote::quote; 5 | use syn::Data; 6 | 7 | use crate::doc::extract_doc; 8 | 9 | static UNSUPPORTED_SHAPE: &str = 10 | "#[derive(ArgOpt)] can only be applied to structs with single unnamed field or enums"; 11 | 12 | #[derive(FromAttributes)] 13 | struct ArgOpt { 14 | long: Option, 15 | 16 | short: Option, 17 | 18 | #[allow(dead_code)] 19 | short_only: Option<()>, 20 | 21 | #[allow(dead_code)] 22 | use_default: Option<()>, 23 | } 24 | 25 | fn derive_parse_fn_from_enum(ty_name: &syn::Ident) -> TokenStream { 26 | quote! { 27 | fn parse(s: &str) -> Option { 28 | <#ty_name as std::str::FromStr>::from_str(s).ok() 29 | } 30 | } 31 | } 32 | 33 | fn derive_parse_fn_from_struct( 34 | struct_token: &syn::token::Struct, 35 | fields: &syn::Fields, 36 | ty_name: &syn::Ident, 37 | ) -> syn::Result { 38 | let field = match fields.iter().collect::>()[..] { 39 | [field] => field, 40 | _ => return Err(syn::Error::new_spanned(struct_token, UNSUPPORTED_SHAPE)), 41 | }; 42 | 43 | let ty = &field.ty; 44 | 45 | Ok(quote! { 46 | fn parse(s: &str) -> Option { 47 | let val = <#ty as std::str::FromStr>::from_str(s).ok()?; 48 | Some(#ty_name(val)) 49 | } 50 | }) 51 | } 52 | 53 | pub fn derive_arg_opt(input: TokenStream) -> syn::Result { 54 | let input = syn::parse2::(input)?; 55 | 56 | let attr = ArgOpt::try_from_attributes(&input.attrs)?; 57 | 58 | let ty_name = &input.ident; 59 | 60 | let long = attr 61 | .as_ref() 62 | .and_then(|arg_opt| arg_opt.long.clone()) 63 | .map_or_else( 64 | || input.ident.to_string().to_case(Case::Kebab), 65 | |lit_str| lit_str.value(), 66 | ); 67 | 68 | let flag = match attr.as_ref().and_then(|arg_opt| arg_opt.short.clone()) { 69 | Some(short) => { 70 | quote! { cli_compose::schema::Flag::BothLongAndShort(#long.to_owned(), #short) } 71 | } 72 | None => quote! { cli_compose::schema::Flag::LongOnly(#long.to_owned()) }, 73 | }; 74 | 75 | let doc = extract_doc(&input.attrs); 76 | 77 | let parse_fn = match &input.data { 78 | Data::Enum(_) => Ok(derive_parse_fn_from_enum(ty_name)), 79 | 80 | Data::Struct(syn::DataStruct { 81 | struct_token, 82 | fields, 83 | .. 84 | }) => derive_parse_fn_from_struct(struct_token, fields, ty_name), 85 | 86 | Data::Union(data_union) => Err(syn::Error::new_spanned( 87 | data_union.union_token, 88 | UNSUPPORTED_SHAPE, 89 | )), 90 | }?; 91 | 92 | let sharp = syn::Token![#](proc_macro2::Span::call_site()); 93 | 94 | Ok(quote! { 95 | impl cli_compose::schema::AsMember for #ty_name { 96 | fn handle(mut builder: cli_compose::schema::CliBuilder) -> cli_compose::schema::CliBuilder { 97 | use cli_compose::schema::{forwarded::quote::quote, AsArgOpt}; 98 | 99 | let flag = format!("{}", <#ty_name as AsArgOpt>::flag()); 100 | 101 | builder.ops.extend(quote! { 102 | println!("ArgOpt {}", #sharp flag); 103 | }); 104 | 105 | builder 106 | } 107 | } 108 | 109 | impl cli_compose::schema::AsArgOpt for #ty_name { 110 | fn flag() -> cli_compose::schema::Flag { 111 | #flag 112 | } 113 | 114 | fn description() -> String { 115 | #doc.to_owned() 116 | } 117 | 118 | #parse_fn 119 | } 120 | }) 121 | } 122 | 123 | #[cfg(test)] 124 | mod tests { 125 | use quote::quote; 126 | 127 | fn test_arg_opt_deriver(input: proc_macro2::TokenStream) -> anyhow::Result { 128 | let tokens = super::derive_arg_opt(input)?; 129 | 130 | crate::pretty_print::pretty_print_rust_code(tokens) 131 | } 132 | 133 | #[test] 134 | fn empty() { 135 | insta::assert_debug_snapshot!(test_arg_opt_deriver(quote! {})); 136 | } 137 | 138 | #[test] 139 | fn struct_without_field() { 140 | insta::assert_debug_snapshot!(test_arg_opt_deriver(quote! { 141 | struct Foo; 142 | })); 143 | } 144 | 145 | #[test] 146 | fn struct_with_single_field() { 147 | insta::assert_display_snapshot!(test_arg_opt_deriver(quote! { 148 | struct Foo(String); 149 | }) 150 | .unwrap()); 151 | } 152 | 153 | #[test] 154 | fn struct_with_multiple_fields() { 155 | insta::assert_debug_snapshot!(test_arg_opt_deriver(quote! { 156 | struct Foo(String, i32); 157 | })); 158 | } 159 | 160 | #[test] 161 | fn _enum() { 162 | insta::assert_display_snapshot!(test_arg_opt_deriver(quote! { 163 | enum Foo { Bar, Baz } 164 | }) 165 | .unwrap()); 166 | } 167 | 168 | #[test] 169 | fn _union() { 170 | insta::assert_debug_snapshot!(test_arg_opt_deriver(quote! { 171 | union Foo { f1: i32, f2: u32 } 172 | })); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /macro/src/deriver/cli.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | 3 | pub fn derive_cli(input: TokenStream) -> syn::Result { 4 | let input: syn::DeriveInput = syn::parse2(input)?; 5 | 6 | let ident = &input.ident; 7 | let ident_str = ident.to_string(); 8 | 9 | Ok(quote::quote! { 10 | impl cli_compose::schema::AsCliMeta for #ident { 11 | fn ident() -> cli_compose::schema::forwarded::syn::Ident { 12 | cli_compose::schema::ident(#ident_str) 13 | } 14 | } 15 | }) 16 | } 17 | 18 | #[cfg(test)] 19 | mod tests { 20 | use quote::quote; 21 | 22 | use super::derive_cli; 23 | 24 | #[test] 25 | fn empty() { 26 | insta::assert_debug_snapshot!(derive_cli(quote! {})); 27 | } 28 | 29 | #[test] 30 | fn empty_struct() { 31 | let input = quote! { struct Cli; }; 32 | insta::assert_display_snapshot!(derive_cli(input) 33 | .map(|tokens| crate::pretty_print::pretty_print_rust_code(tokens).unwrap()) 34 | .unwrap()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /macro/src/deriver/from_kebab_str.rs: -------------------------------------------------------------------------------- 1 | use convert_case::{Case, Casing}; 2 | use proc_macro2::TokenStream; 3 | use quote::quote; 4 | use syn::Data; 5 | 6 | static UNSUPPORTED_SHAPE: &str = 7 | "`#[derive(FromKebabStr)]` can only be applied to enums whose variants have no field"; 8 | 9 | pub fn derive_from_kebab_str(input: TokenStream) -> syn::Result { 10 | let input = syn::parse2::(input)?; 11 | 12 | let ty_name = &input.ident; 13 | 14 | let data_enum = match &input.data { 15 | Data::Enum(data_enum) => Ok(data_enum), 16 | Data::Struct(data_struct) => Err(syn::Error::new_spanned( 17 | data_struct.struct_token, 18 | UNSUPPORTED_SHAPE, 19 | )), 20 | Data::Union(data_union) => Err(syn::Error::new_spanned( 21 | data_union.union_token, 22 | UNSUPPORTED_SHAPE, 23 | )), 24 | }?; 25 | 26 | let arms = data_enum 27 | .variants 28 | .iter() 29 | .map(|variant| { 30 | let variant_str = variant.ident.to_string().to_case(Case::Kebab); 31 | quote! { #variant_str => Ok(#ty_name::#variant), } 32 | }) 33 | .collect::(); 34 | 35 | Ok(quote! { 36 | impl std::str::FromStr for #ty_name { 37 | type Err = (); 38 | 39 | fn from_str(s: &str) -> Result { 40 | match s { 41 | #arms 42 | _ => Err(()), 43 | } 44 | } 45 | } 46 | }) 47 | } 48 | 49 | #[cfg(test)] 50 | mod tests { 51 | use quote::quote; 52 | 53 | fn test_from_kebab_str_deriver(input: proc_macro2::TokenStream) -> anyhow::Result { 54 | let tokens = super::derive_from_kebab_str(input)?; 55 | 56 | crate::pretty_print::pretty_print_rust_code(tokens) 57 | } 58 | 59 | #[test] 60 | fn empty() { 61 | insta::assert_debug_snapshot!(test_from_kebab_str_deriver(quote! {})); 62 | } 63 | 64 | #[test] 65 | fn _enum() { 66 | let input = quote! { 67 | enum TextFileFormat { 68 | Json, 69 | Yaml, 70 | ReStructuredText, 71 | } 72 | }; 73 | insta::assert_display_snapshot!(test_from_kebab_str_deriver(input).unwrap()); 74 | } 75 | 76 | #[test] 77 | fn _struct() { 78 | let input = quote! { 79 | struct Foo; 80 | }; 81 | insta::assert_debug_snapshot!(test_from_kebab_str_deriver(input)); 82 | } 83 | 84 | #[test] 85 | fn _union() { 86 | insta::assert_debug_snapshot!(test_from_kebab_str_deriver(quote! { 87 | union Foo { f1: i32, f2: u32 } 88 | })); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /macro/src/deriver/multi_select.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | 3 | pub fn derive_multi_select(_: TokenStream) -> syn::Result { 4 | Ok(TokenStream::new()) 5 | } 6 | -------------------------------------------------------------------------------- /macro/src/deriver/opt.rs: -------------------------------------------------------------------------------- 1 | use bae::FromAttributes; 2 | use convert_case::{Case, Casing}; 3 | use proc_macro2::TokenStream; 4 | use quote::quote; 5 | 6 | use crate::doc::extract_doc; 7 | 8 | #[derive(FromAttributes)] 9 | struct Opt { 10 | long: Option, 11 | 12 | short: Option, 13 | 14 | #[allow(dead_code)] 15 | short_only: Option<()>, 16 | } 17 | 18 | pub fn derive_opt(input: TokenStream) -> syn::Result { 19 | let input = syn::parse2::(input)?; 20 | 21 | let attr = Opt::try_from_attributes(&input.attrs)?; 22 | 23 | let struct_name = &input.ident; 24 | let long = attr 25 | .as_ref() 26 | .and_then(|attr| attr.long.clone()) 27 | .map_or_else( 28 | || struct_name.to_string().to_case(Case::Kebab), 29 | |lit_str| lit_str.value(), 30 | ); 31 | 32 | let flag = match &attr.and_then(|opt| opt.short) { 33 | Some(short) => { 34 | quote! { cli_compose::schema::Flag::BothLongAndShort(#long.to_owned(), #short) } 35 | } 36 | None => quote! { cli_compose::schema::Flag::LongOnly(#long.to_owned()) }, 37 | }; 38 | 39 | let doc = extract_doc(&input.attrs); 40 | 41 | let sharp = syn::Token![#](proc_macro2::Span::call_site()); 42 | 43 | Ok(quote! { 44 | impl cli_compose::schema::AsMember for #struct_name { 45 | fn handle(mut builder: cli_compose::schema::CliBuilder) -> cli_compose::schema::CliBuilder { 46 | use cli_compose::schema::{forwarded::quote::quote, AsOpt}; 47 | 48 | let flag = format!("{}", <#struct_name as AsOpt>::flag()); 49 | 50 | builder.ops.extend(quote! { 51 | println!("ArgOpt {}", #sharp flag); 52 | }); 53 | 54 | builder 55 | } 56 | } 57 | 58 | impl cli_compose::schema::AsOpt for #struct_name { 59 | fn flag() -> cli_compose::schema::Flag { 60 | #flag 61 | } 62 | 63 | fn description() -> String { 64 | #doc.to_owned() 65 | } 66 | } 67 | }) 68 | } 69 | 70 | #[cfg(test)] 71 | mod tests { 72 | use quote::quote; 73 | 74 | fn test_opt_deriver(input: proc_macro2::TokenStream) -> anyhow::Result { 75 | let tokens = super::derive_opt(input)?; 76 | 77 | crate::pretty_print::pretty_print_rust_code(tokens) 78 | } 79 | 80 | #[test] 81 | fn empty() { 82 | insta::assert_debug_snapshot!(test_opt_deriver(quote! {})); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /macro/src/deriver/pos_arg.rs: -------------------------------------------------------------------------------- 1 | use bae::FromAttributes; 2 | use convert_case::{Case, Casing}; 3 | use proc_macro2::TokenStream; 4 | use quote::{quote, ToTokens}; 5 | use syn::Data; 6 | 7 | use crate::doc::extract_doc; 8 | 9 | #[derive(FromAttributes)] 10 | struct PosArg { 11 | name: Option, 12 | 13 | #[allow(dead_code)] 14 | use_default: Option<()>, 15 | } 16 | 17 | pub fn derive_pos_arg(input: TokenStream) -> syn::Result { 18 | let input = syn::parse2::(input)?; 19 | 20 | let attr = PosArg::try_from_attributes(&input.attrs)?; 21 | 22 | let doc = extract_doc(&input.attrs); 23 | 24 | let ty_name = &input.ident; 25 | let ty_name_str = ty_name.to_string(); 26 | let ty_name_kebab_case = attr.and_then(|attr| attr.name).map_or_else( 27 | || ty_name_str.to_case(Case::Kebab), 28 | |lit_str| lit_str.value(), 29 | ); 30 | 31 | let methods = match &input.data { 32 | Data::Enum(_) => { 33 | quote! { 34 | fn parse(s: &str) -> Option { 35 | <#ty_name as std::str::FromStr>::from_str(s).ok() 36 | } 37 | 38 | fn result() -> cli_compose::schema::forwarded::syn::Type { 39 | cli_compose::schema::forwarded::syn::parse_str(#ty_name_str).unwrap() 40 | } 41 | } 42 | } 43 | 44 | Data::Struct(data_struct) => match data_struct.fields.iter().collect::>()[..] { 45 | [field] => { 46 | let ty = &field.ty; 47 | let ty_name = ty.into_token_stream().to_string(); 48 | let parse_method = if let Some(ident) = &field.ident { 49 | quote! { 50 | fn parse(s: &str) -> Option { 51 | <#ty as std::str::FromStr>::from_str(s).ok().map(|v| Self { #ident: v }) 52 | } 53 | } 54 | } else { 55 | quote! { 56 | fn parse(s: &str) -> Option { 57 | <#ty as std::str::FromStr>::from_str(s).ok().map(Self) 58 | } 59 | } 60 | }; 61 | quote! { 62 | #parse_method 63 | 64 | fn result() -> cli_compose::schema::forwarded::syn::Type { 65 | cli_compose::schema::forwarded::syn::parse_str(#ty_name).unwrap() 66 | } 67 | } 68 | } 69 | [] => return Err(syn::Error::new_spanned(input.ident, "missing field")), 70 | _ => { 71 | return Err(syn::Error::new_spanned( 72 | data_struct.struct_token, 73 | "multiple fields are not allowed", 74 | )) 75 | } 76 | }, 77 | 78 | Data::Union(data_union) => { 79 | return Err(syn::Error::new_spanned( 80 | data_union.union_token, 81 | "unions are not allowed", 82 | )); 83 | } 84 | }; 85 | 86 | let sharp = syn::Token![#](proc_macro2::Span::call_site()); 87 | 88 | Ok(quote::quote! { 89 | impl cli_compose::schema::AsMember for #ty_name { 90 | fn handle(mut builder: cli_compose::schema::CliBuilder) -> cli_compose::schema::CliBuilder { 91 | use cli_compose::schema::{forwarded::{syn, quote}, AsPosArg}; 92 | 93 | let name = <#ty_name as AsPosArg>::name(); 94 | 95 | let res_ty = 96 | ::into_token_stream( 97 | <#ty_name as AsPosArg>::result() 98 | ).to_string(); 99 | 100 | builder.ops.extend(quote::quote! { 101 | println!("PosArg {} ({})", #sharp name, #sharp res_ty); 102 | }); 103 | 104 | builder 105 | } 106 | } 107 | 108 | impl cli_compose::schema::AsPosArg for #ty_name { 109 | fn name() -> String { 110 | #ty_name_kebab_case.to_owned() 111 | } 112 | 113 | fn description() -> String { 114 | #doc.to_owned() 115 | } 116 | 117 | #methods 118 | } 119 | }) 120 | } 121 | 122 | #[cfg(test)] 123 | mod tests { 124 | use quote::quote; 125 | 126 | fn test_pos_arg_deriver(input: proc_macro2::TokenStream) -> anyhow::Result { 127 | let tokens = super::derive_pos_arg(input)?; 128 | 129 | crate::pretty_print::pretty_print_rust_code(tokens) 130 | } 131 | 132 | #[test] 133 | fn empty() { 134 | insta::assert_debug_snapshot!(test_pos_arg_deriver(quote! {})); 135 | } 136 | 137 | #[test] 138 | fn struct_without_field() { 139 | insta::assert_debug_snapshot!(test_pos_arg_deriver(quote! { 140 | struct Foo; 141 | })); 142 | } 143 | 144 | #[test] 145 | fn struct_with_single_field() { 146 | insta::assert_display_snapshot!(test_pos_arg_deriver(quote! { 147 | struct Foo(String); 148 | }) 149 | .unwrap()); 150 | } 151 | 152 | #[test] 153 | fn struct_with_multiple_fields() { 154 | insta::assert_debug_snapshot!(test_pos_arg_deriver(quote! { 155 | struct Foo(String, i32); 156 | })); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /macro/src/deriver/single_select.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | 3 | pub fn derive_single_select(_: TokenStream) -> syn::Result { 4 | Ok(TokenStream::new()) 5 | } 6 | -------------------------------------------------------------------------------- /macro/src/deriver/snapshots/cli_compose_macro__deriver__arg_opt__tests___enum.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: macro/src/deriver/arg_opt.rs 3 | assertion_line: 162 4 | expression: "test_arg_opt_deriver(quote! { enum Foo { Bar, Baz } }).unwrap()" 5 | --- 6 | impl cli_compose::schema::AsMember for Foo { 7 | fn handle(mut builder: cli_compose::schema::CliBuilder) -> cli_compose::schema::CliBuilder { 8 | use cli_compose::schema::{forwarded::quote::quote, AsArgOpt}; 9 | let flag = format!("{}", ::flag()); 10 | builder 11 | .ops 12 | .extend(quote! { println ! ("ArgOpt {}" , # flag) ; }); 13 | builder 14 | } 15 | } 16 | impl cli_compose::schema::AsArgOpt for Foo { 17 | fn flag() -> cli_compose::schema::Flag { 18 | cli_compose::schema::Flag::LongOnly("foo".to_owned()) 19 | } 20 | fn description() -> String { 21 | "".to_owned() 22 | } 23 | fn parse(s: &str) -> Option { 24 | ::from_str(s).ok() 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /macro/src/deriver/snapshots/cli_compose_macro__deriver__arg_opt__tests___union.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: macro/src/deriver/arg_opt.rs 3 | assertion_line: 170 4 | expression: "test_arg_opt_deriver(quote! { union Foo { f1 : i32, f2 : u32 } })" 5 | --- 6 | Err( 7 | Error( 8 | "#[derive(ArgOpt)] can only be applied to structs with single unnamed field or enums", 9 | ), 10 | ) 11 | -------------------------------------------------------------------------------- /macro/src/deriver/snapshots/cli_compose_macro__deriver__arg_opt__tests__empty.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: macro/src/deriver/arg_opt.rs 3 | assertion_line: 135 4 | expression: "test_arg_opt_deriver(quote! {})" 5 | --- 6 | Err( 7 | Error( 8 | "unexpected end of input, expected one of: `struct`, `enum`, `union`", 9 | ), 10 | ) 11 | -------------------------------------------------------------------------------- /macro/src/deriver/snapshots/cli_compose_macro__deriver__arg_opt__tests__struct_with_multiple_fields.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: macro/src/deriver/arg_opt.rs 3 | assertion_line: 155 4 | expression: "test_arg_opt_deriver(quote! { struct Foo(String, i32) ; })" 5 | --- 6 | Err( 7 | Error( 8 | "#[derive(ArgOpt)] can only be applied to structs with single unnamed field or enums", 9 | ), 10 | ) 11 | -------------------------------------------------------------------------------- /macro/src/deriver/snapshots/cli_compose_macro__deriver__arg_opt__tests__struct_with_single_field.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: macro/src/deriver/arg_opt.rs 3 | assertion_line: 147 4 | expression: "test_arg_opt_deriver(quote! { struct Foo(String) ; }).unwrap()" 5 | --- 6 | impl cli_compose::schema::AsMember for Foo { 7 | fn handle(mut builder: cli_compose::schema::CliBuilder) -> cli_compose::schema::CliBuilder { 8 | use cli_compose::schema::{forwarded::quote::quote, AsArgOpt}; 9 | let flag = format!("{}", ::flag()); 10 | builder 11 | .ops 12 | .extend(quote! { println ! ("ArgOpt {}" , # flag) ; }); 13 | builder 14 | } 15 | } 16 | impl cli_compose::schema::AsArgOpt for Foo { 17 | fn flag() -> cli_compose::schema::Flag { 18 | cli_compose::schema::Flag::LongOnly("foo".to_owned()) 19 | } 20 | fn description() -> String { 21 | "".to_owned() 22 | } 23 | fn parse(s: &str) -> Option { 24 | let val = ::from_str(s).ok()?; 25 | Some(Foo(val)) 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /macro/src/deriver/snapshots/cli_compose_macro__deriver__arg_opt__tests__struct_without_field.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: macro/src/deriver/arg_opt.rs 3 | assertion_line: 140 4 | expression: "test_arg_opt_deriver(quote! { struct Foo ; })" 5 | --- 6 | Err( 7 | Error( 8 | "#[derive(ArgOpt)] can only be applied to structs with single unnamed field or enums", 9 | ), 10 | ) 11 | -------------------------------------------------------------------------------- /macro/src/deriver/snapshots/cli_compose_macro__deriver__cli__tests__empty.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: macro/src/deriver/cli.rs 3 | assertion_line: 27 4 | expression: derive_cli(input) 5 | --- 6 | Err( 7 | Error( 8 | "unexpected end of input, expected one of: `struct`, `enum`, `union`", 9 | ), 10 | ) 11 | -------------------------------------------------------------------------------- /macro/src/deriver/snapshots/cli_compose_macro__deriver__cli__tests__empty_struct.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: macro/src/deriver/cli.rs 3 | assertion_line: 33 4 | expression: "derive_cli(input).map(|tokens|\n crate::pretty_print::pretty_print_rust_code(tokens).unwrap()).unwrap()" 5 | --- 6 | impl cli_compose::schema::AsCliMeta for Cli { 7 | fn ident() -> cli_compose::schema::forwarded::syn::Ident { 8 | cli_compose::schema::ident("Cli") 9 | } 10 | } 11 | 12 | -------------------------------------------------------------------------------- /macro/src/deriver/snapshots/cli_compose_macro__deriver__from_kebab_str__tests___enum.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: macro/src/deriver/from_kebab_str.rs 3 | assertion_line: 73 4 | expression: test_from_kebab_str_deriver(input).unwrap() 5 | --- 6 | impl std::str::FromStr for TextFileFormat { 7 | type Err = (); 8 | fn from_str(s: &str) -> Result { 9 | match s { 10 | "json" => Ok(TextFileFormat::Json), 11 | "yaml" => Ok(TextFileFormat::Yaml), 12 | "re-structured-text" => Ok(TextFileFormat::ReStructuredText), 13 | _ => Err(()), 14 | } 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /macro/src/deriver/snapshots/cli_compose_macro__deriver__from_kebab_str__tests___struct.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: macro/src/deriver/from_kebab_str.rs 3 | expression: test_from_kebab_str_deriver(input) 4 | --- 5 | Err( 6 | Error( 7 | "`#[derive(FromKebabStr)]` can only be applied to enums whose variants have no field", 8 | ), 9 | ) 10 | -------------------------------------------------------------------------------- /macro/src/deriver/snapshots/cli_compose_macro__deriver__from_kebab_str__tests___union.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: macro/src/deriver/from_kebab_str.rs 3 | assertion_line: 86 4 | expression: "test_from_kebab_str_deriver(quote! { union Foo { f1 : i32, f2 : u32 } })" 5 | --- 6 | Err( 7 | Error( 8 | "`#[derive(FromKebabStr)]` can only be applied to enums whose variants have no field", 9 | ), 10 | ) 11 | -------------------------------------------------------------------------------- /macro/src/deriver/snapshots/cli_compose_macro__deriver__from_kebab_str__tests__empty.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: macro/src/deriver/from_kebab_str.rs 3 | assertion_line: 61 4 | expression: "test_from_kebab_str_deriver(quote! {})" 5 | --- 6 | Err( 7 | Error( 8 | "unexpected end of input, expected one of: `struct`, `enum`, `union`", 9 | ), 10 | ) 11 | -------------------------------------------------------------------------------- /macro/src/deriver/snapshots/cli_compose_macro__deriver__opt__tests__empty.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: macro/src/deriver/opt.rs 3 | assertion_line: 82 4 | expression: "test_opt_deriver(quote! {})" 5 | --- 6 | Err( 7 | Error( 8 | "unexpected end of input, expected one of: `struct`, `enum`, `union`", 9 | ), 10 | ) 11 | -------------------------------------------------------------------------------- /macro/src/deriver/snapshots/cli_compose_macro__deriver__pos_arg__tests__empty.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: macro/src/deriver/pos_arg.rs 3 | assertion_line: 134 4 | expression: "test_pos_arg_deriver(quote! {})" 5 | --- 6 | Err( 7 | Error( 8 | "unexpected end of input, expected one of: `struct`, `enum`, `union`", 9 | ), 10 | ) 11 | -------------------------------------------------------------------------------- /macro/src/deriver/snapshots/cli_compose_macro__deriver__pos_arg__tests__struct_with_multiple_fields.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: macro/src/deriver/pos_arg.rs 3 | assertion_line: 154 4 | expression: "test_pos_arg_deriver(quote! { struct Foo(String, i32) ; })" 5 | --- 6 | Err( 7 | Error( 8 | "multiple fields are not allowed", 9 | ), 10 | ) 11 | -------------------------------------------------------------------------------- /macro/src/deriver/snapshots/cli_compose_macro__deriver__pos_arg__tests__struct_with_single_field.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: macro/src/deriver/pos_arg.rs 3 | assertion_line: 146 4 | expression: "test_pos_arg_deriver(quote! { struct Foo(String) ; }).unwrap()" 5 | --- 6 | impl cli_compose::schema::AsMember for Foo { 7 | fn handle(mut builder: cli_compose::schema::CliBuilder) -> cli_compose::schema::CliBuilder { 8 | use cli_compose::schema::{ 9 | forwarded::{quote, syn}, 10 | AsPosArg, 11 | }; 12 | let name = ::name(); 13 | let res_ty = ::into_token_stream(::result()) 14 | .to_string(); 15 | builder 16 | .ops 17 | .extend(quote::quote! { println ! ("PosArg {} ({})" , # name , # res_ty) ; }); 18 | builder 19 | } 20 | } 21 | impl cli_compose::schema::AsPosArg for Foo { 22 | fn name() -> String { 23 | "foo".to_owned() 24 | } 25 | fn description() -> String { 26 | "".to_owned() 27 | } 28 | fn parse(s: &str) -> Option { 29 | ::from_str(s).ok().map(Self) 30 | } 31 | fn result() -> cli_compose::schema::forwarded::syn::Type { 32 | cli_compose::schema::forwarded::syn::parse_str("String").unwrap() 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /macro/src/deriver/snapshots/cli_compose_macro__deriver__pos_arg__tests__struct_without_field.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: macro/src/deriver/pos_arg.rs 3 | assertion_line: 139 4 | expression: "test_pos_arg_deriver(quote! { struct Foo ; })" 5 | --- 6 | Err( 7 | Error( 8 | "missing field", 9 | ), 10 | ) 11 | -------------------------------------------------------------------------------- /macro/src/doc.rs: -------------------------------------------------------------------------------- 1 | use syn::{Attribute, Lit, Meta}; 2 | 3 | fn try_get_single_line_doc(attr: &Attribute) -> Option { 4 | let meta = attr.parse_meta().ok()?; 5 | 6 | let lit_str = match meta { 7 | Meta::NameValue(syn::MetaNameValue { 8 | path, 9 | lit: Lit::Str(lit_str), 10 | .. 11 | }) if path.is_ident("doc") => lit_str, 12 | _ => return None, 13 | }; 14 | 15 | Some(lit_str.value().trim_start().to_owned()) 16 | } 17 | 18 | pub fn extract_doc(attrs: &[Attribute]) -> String { 19 | attrs 20 | .iter() 21 | .filter_map(try_get_single_line_doc) 22 | .collect::>() 23 | .join("\n") 24 | } 25 | -------------------------------------------------------------------------------- /macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod deriver; 2 | mod doc; 3 | mod pretty_print; 4 | mod use_cli; 5 | 6 | use proc_macro::TokenStream; 7 | 8 | macro_rules! wrap_derive_fn { 9 | ($f:expr, $input:expr) => { 10 | $f($input.into()) 11 | .unwrap_or_else(syn::Error::into_compile_error) 12 | .into() 13 | }; 14 | } 15 | 16 | /// Implements [`std::str::FromStr`] for enum types consisting only of variants without fields 17 | /// so that they can be parsed from kebab case strings. 18 | #[cfg(not(tarpaulin_include))] 19 | #[proc_macro_derive(FromKebabStr)] 20 | pub fn derive_from_kebab_str(input: TokenStream) -> TokenStream { 21 | wrap_derive_fn!(deriver::derive_from_kebab_str, input) 22 | } 23 | 24 | /// Positional argument 25 | /// 26 | /// Avaliable attributes: `name`, `use_default` 27 | #[cfg(not(tarpaulin_include))] 28 | #[proc_macro_derive(PosArg, attributes(pos_arg))] 29 | pub fn derive_pos_arg(input: TokenStream) -> TokenStream { 30 | wrap_derive_fn!(deriver::derive_pos_arg, input) 31 | } 32 | 33 | /// Option with argument 34 | /// 35 | /// Avaliable attributes: `short`, `short_only`, `long`, `use_default` 36 | #[cfg(not(tarpaulin_include))] 37 | #[proc_macro_derive(ArgOpt, attributes(arg_opt))] 38 | pub fn derive_arg_opt(input: TokenStream) -> TokenStream { 39 | wrap_derive_fn!(deriver::derive_arg_opt, input) 40 | } 41 | 42 | /// Option without argument 43 | /// 44 | /// Available attributes: `short`, `short_only`, `long`, `use_default` 45 | #[cfg(not(tarpaulin_include))] 46 | #[proc_macro_derive(Opt, attributes(opt))] 47 | pub fn derive_opt(input: TokenStream) -> TokenStream { 48 | wrap_derive_fn!(deriver::derive_opt, input) 49 | } 50 | 51 | #[cfg(not(tarpaulin_include))] 52 | #[proc_macro_derive(SingleSelect)] 53 | pub fn derive_single_select(input: TokenStream) -> TokenStream { 54 | wrap_derive_fn!(deriver::derive_single_select, input) 55 | } 56 | 57 | #[cfg(not(tarpaulin_include))] 58 | #[proc_macro_derive(MultiSelect)] 59 | pub fn derive_multi_select(input: TokenStream) -> TokenStream { 60 | wrap_derive_fn!(deriver::derive_multi_select, input) 61 | } 62 | 63 | #[cfg(not(tarpaulin_include))] 64 | #[proc_macro_derive(Cli, attributes(cli))] 65 | pub fn derive_cli(input: TokenStream) -> TokenStream { 66 | wrap_derive_fn!(deriver::derive_cli, input) 67 | } 68 | 69 | /// Expands the source code generated by [`cli_compose::codegen::define_cli`] 70 | /// and then `use`s the specified struct. 71 | /// 72 | /// ## Syntax 73 | /// 74 | /// ```text 75 | /// path 76 | /// ``` 77 | #[cfg(not(tarpaulin_include))] 78 | #[proc_macro] 79 | pub fn use_cli(input: TokenStream) -> TokenStream { 80 | wrap_derive_fn!(use_cli::use_cli, input) 81 | } 82 | -------------------------------------------------------------------------------- /macro/src/pretty_print.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | use std::process; 3 | 4 | #[allow(dead_code)] 5 | pub fn pretty_print_rust_code(tokens: proc_macro2::TokenStream) -> anyhow::Result { 6 | let mut rustfmt = process::Command::new("rustfmt") 7 | .stdin(process::Stdio::piped()) 8 | .stdout(process::Stdio::piped()) 9 | .spawn()?; 10 | 11 | write!(rustfmt.stdin.take().unwrap(), "{}", tokens)?; 12 | 13 | let output = rustfmt.wait_with_output()?; 14 | 15 | let stdout = String::from_utf8(output.stdout)?; 16 | 17 | Ok(stdout) 18 | } 19 | -------------------------------------------------------------------------------- /macro/src/snapshots/cli_compose_macro__use_cli__tests__use_cli.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: macro/src/use_cli.rs 3 | assertion_line: 32 4 | expression: "super::use_cli(input).map(|tokens|\n crate::pretty_print::pretty_print_rust_code(tokens).unwrap()).unwrap()" 5 | --- 6 | include!(concat!(env!("OUT_DIR"), "/cli_compose/example-opts.rs")); 7 | use opts::ExampleOpts; 8 | 9 | -------------------------------------------------------------------------------- /macro/src/use_cli.rs: -------------------------------------------------------------------------------- 1 | use convert_case::{Case, Casing}; 2 | use proc_macro2::TokenStream; 3 | use std::path::MAIN_SEPARATOR; 4 | 5 | pub fn use_cli(input: TokenStream) -> syn::Result { 6 | let path = syn::parse2::(input)?; 7 | 8 | let ident = &path 9 | .segments 10 | .iter() 11 | .last() 12 | .expect("Failed to extract the last path segment") 13 | .ident; 14 | 15 | let source_path = format!( 16 | "{sep}cli_compose{sep}{filename}.rs", 17 | sep = MAIN_SEPARATOR, 18 | filename = ident.to_string().to_case(Case::Kebab), 19 | ); 20 | 21 | Ok(quote::quote! { 22 | include!(concat!(env!("OUT_DIR"), #source_path)); 23 | use #path; 24 | }) 25 | } 26 | 27 | #[cfg(test)] 28 | mod tests { 29 | #[test] 30 | fn test_use_cli() { 31 | let input = quote::quote! { opts::ExampleOpts }; 32 | insta::assert_display_snapshot!(super::use_cli(input) 33 | .map(|tokens| crate::pretty_print::pretty_print_rust_code(tokens).unwrap()) 34 | .unwrap()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base"], 3 | "timezone": "Asia/Tokyo", 4 | "schedule": ["every weekend"], 5 | "assignees": ["@0918nobita"], 6 | "patch": { "automerge": true }, 7 | "lockFileMaintenance": { 8 | "enabled": true, 9 | "schedule": ["before 3am on Monday"] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/codegen.rs: -------------------------------------------------------------------------------- 1 | use crate::schema_impl::{AsCliMeta, CliBuilder, CliBuilderResult}; 2 | 3 | /// Creates Rust source code generator. 4 | /// The generated code defines a new struct representing the result of parsing 5 | /// and implements [`cli_compose::runtime::AsCli`] trait for the specified struct. 6 | pub fn define_cli(base_path: &str) -> CliBuilderResult { 7 | CliBuilder::new::(base_path) 8 | } 9 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Composable, strict CLI framework with static analysis for Rust 2 | 3 | #[cfg(any(feature = "schema", feature = "codegen"))] 4 | mod schema_impl; 5 | 6 | #[cfg_attr( 7 | all(feature = "schema", not(feature = "codegen"), not(feature = "runtime")), 8 | doc = r##" 9 | ```compile_fail 10 | // Cannot use `runtime` mod without `runtime` feature 11 | use cli_compose::runtime::*; 12 | ``` 13 | 14 | ```compile_fail 15 | // Cannot use `codegen` mod without `codegen` feature 16 | use cli_compose::codegen::*; 17 | ``` 18 | "## 19 | )] 20 | #[cfg(feature = "schema")] 21 | pub mod schema { 22 | pub use crate::schema_impl::*; 23 | 24 | pub use cli_compose_macro::{ 25 | ArgOpt, Cli, FromKebabStr, MultiSelect, Opt, PosArg, SingleSelect, 26 | }; 27 | 28 | pub mod forwarded { 29 | pub use quote; 30 | pub use syn; 31 | } 32 | } 33 | 34 | #[cfg_attr( 35 | all(feature = "codegen", not(feature = "runtime"), not(feature = "schema")), 36 | doc = r##" 37 | ```compile_fail 38 | // Cannot use `runtime` mod without `runtime` feature 39 | use cli_compose::runtime::*; 40 | ``` 41 | 42 | ```compile_fail 43 | // Cannot use `schema` mod without `schema` feature 44 | use cli_compose::schema::*; 45 | ``` 46 | "## 47 | )] 48 | #[cfg(feature = "codegen")] 49 | pub mod codegen; 50 | 51 | #[cfg_attr( 52 | all(feature = "runtime", not(feature = "codegen"), not(feature = "schema")), 53 | doc = r##" 54 | ```compile_fail 55 | // Cannot use `codegen` mod without `codegen` feature 56 | use cli_compose::codegen::*; 57 | ``` 58 | 59 | ```compile_fail 60 | // Cannot use `schema` mod without `schema` feature 61 | use cli_compose::schema::*; 62 | ``` 63 | "## 64 | )] 65 | #[cfg(feature = "runtime")] 66 | pub mod runtime; 67 | -------------------------------------------------------------------------------- /src/runtime.rs: -------------------------------------------------------------------------------- 1 | pub use cli_compose_macro::use_cli; 2 | 3 | pub trait AsCli { 4 | fn parse(args: impl Iterator) -> R; 5 | } 6 | 7 | #[derive(Debug, PartialEq)] 8 | pub enum Token { 9 | Long(String), 10 | Short(char), 11 | Value(String), 12 | } 13 | 14 | pub fn parse_into_tokens(args: A) -> impl Iterator 15 | where 16 | A: Iterator, 17 | { 18 | args.skip(1).flat_map(|arg| { 19 | if let Some(flag) = arg.strip_prefix("--") { 20 | return vec![Token::Long(flag.to_owned())]; 21 | } 22 | if let Some(cs) = arg.strip_prefix('-') { 23 | return cs.chars().map(Token::Short).collect::>(); 24 | } 25 | vec![Token::Value(arg)] 26 | }) 27 | } 28 | 29 | #[cfg(test)] 30 | mod tests { 31 | use insta::assert_debug_snapshot; 32 | use str_macro::str; 33 | 34 | use super::parse_into_tokens; 35 | 36 | #[test] 37 | fn test_long_flag() { 38 | assert_debug_snapshot!(parse_into_tokens( 39 | vec![str!("example"), str!("--input-format"), str!("json")].into_iter() 40 | ) 41 | .collect::>()); 42 | } 43 | 44 | #[test] 45 | fn test_short_flags() { 46 | assert_debug_snapshot!(parse_into_tokens( 47 | vec![str!("example"), str!("-vo"), str!("out.json")].into_iter() 48 | ) 49 | .collect::>()) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/schema_impl.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | 3 | use convert_case::{Case, Casing}; 4 | use derive_more::Display; 5 | use proc_macro2::TokenStream; 6 | use thiserror::Error; 7 | 8 | #[derive(Display)] 9 | pub enum Flag { 10 | #[display(fmt = "--{}", _0)] 11 | LongOnly(String), 12 | 13 | #[display(fmt = "-{}", _0)] 14 | ShortOnly(char), 15 | 16 | #[display(fmt = "--{}, -{}", _0, _1)] 17 | BothLongAndShort(String, char), 18 | } 19 | 20 | pub trait AsMember { 21 | fn handle(builder: CliBuilder) -> CliBuilder; 22 | } 23 | 24 | pub trait AsPosArg: Sized + AsMember { 25 | fn name() -> String; 26 | 27 | fn description() -> String; 28 | 29 | fn parse(s: &str) -> Option; 30 | 31 | fn result() -> syn::Type; 32 | } 33 | 34 | pub trait AsArgOpt: Sized + AsMember { 35 | fn flag() -> Flag; 36 | 37 | fn description() -> String; 38 | 39 | fn parse(s: &str) -> Option; 40 | } 41 | 42 | pub trait AsOpt: AsMember { 43 | fn flag() -> Flag; 44 | 45 | fn description() -> String; 46 | } 47 | 48 | pub trait AsSingleSelect: AsMember { 49 | fn name() -> String; 50 | 51 | fn description() -> String; 52 | } 53 | 54 | pub trait AsMultiSelect: AsMember { 55 | fn name() -> String; 56 | 57 | fn description() -> String; 58 | } 59 | 60 | pub trait AsCliMeta { 61 | fn ident() -> syn::Ident; 62 | } 63 | 64 | pub fn ident(name: &str) -> syn::Ident { 65 | syn::Ident::new(name, proc_macro2::Span::call_site()) 66 | } 67 | 68 | #[derive(Debug, Error)] 69 | pub enum CliBuilderError { 70 | #[error("The base path is invalid")] 71 | InvalidBasePath, 72 | 73 | #[error("The result type is invalid")] 74 | InvalidResultTypeName, 75 | 76 | #[error(transparent)] 77 | Other(#[from] anyhow::Error), 78 | } 79 | 80 | pub struct CliBuilder { 81 | base_path: syn::Path, 82 | cli_ty: syn::Ident, 83 | pub ops: TokenStream, 84 | } 85 | 86 | pub type CliBuilderResult = Result; 87 | 88 | impl CliBuilder { 89 | pub fn new(base_path: &str) -> CliBuilderResult { 90 | let base_path = syn::parse_str(base_path).map_err(|_| CliBuilderError::InvalidBasePath)?; 91 | 92 | Ok(CliBuilder { 93 | base_path, 94 | cli_ty: Cli::ident(), 95 | ops: TokenStream::new(), 96 | }) 97 | } 98 | 99 | pub fn member(self) -> Self { 100 | M::handle(self) 101 | } 102 | 103 | pub fn build(self, result_type_name: &str) -> Result<(), CliBuilderError> { 104 | let out_dir = std::env::var("OUT_DIR").map_err(|e| CliBuilderError::Other(e.into()))?; 105 | 106 | let dest_dir = std::path::Path::new(&out_dir).join("cli_compose"); 107 | 108 | fs::create_dir_all(&dest_dir).map_err(|e| CliBuilderError::Other(e.into()))?; 109 | 110 | let mut dest = dest_dir 111 | .as_path() 112 | .join(self.cli_ty.to_string().to_case(Case::Snake)); 113 | dest.set_extension("rs"); 114 | 115 | let result_type: syn::Ident = 116 | syn::parse_str(result_type_name).map_err(|_| CliBuilderError::InvalidResultTypeName)?; 117 | 118 | let base_path = self.base_path; 119 | let cli_ty = self.cli_ty; 120 | let ops = self.ops; 121 | 122 | let contents = quote::quote! { 123 | struct #result_type { 124 | } 125 | 126 | impl cli_compose::runtime::AsCli<#result_type> for #base_path::#cli_ty { 127 | fn parse(args: impl Iterator) -> #result_type { 128 | let tokens = cli_compose::runtime::parse_into_tokens(args).collect::>(); 129 | println!("tokens: {:?}", tokens); 130 | #ops 131 | todo!() 132 | } 133 | } 134 | }; 135 | 136 | fs::write(&dest, contents.to_string()).map_err(|e| CliBuilderError::Other(e.into())) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/snapshots/cli_compose__runtime__tests__long_flag.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/runtime.rs 3 | assertion_line: 38 4 | expression: "parse_into_tokens(vec![str! (\"example\"), str! (\"--input-format\"), str!\n (\"json\")].into_iter()).collect::>()" 5 | --- 6 | [ 7 | Long( 8 | "input-format", 9 | ), 10 | Value( 11 | "json", 12 | ), 13 | ] 14 | -------------------------------------------------------------------------------- /src/snapshots/cli_compose__runtime__tests__short_flags.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/runtime.rs 3 | assertion_line: 46 4 | expression: "parse_into_tokens(vec![str! (\"example\"), str! (\"-vo\"), str!\n (\"out.json\")].into_iter()).collect::>()" 5 | --- 6 | [ 7 | Short( 8 | 'v', 9 | ), 10 | Short( 11 | 'o', 12 | ), 13 | Value( 14 | "out.json", 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /tarpaulin.toml: -------------------------------------------------------------------------------- 1 | [coverage] 2 | packages = [ "cli-compose", "cli-compose-macro" ] 3 | all-features = true 4 | verbose = true 5 | 6 | [report] 7 | out = [ "Xml" ] 8 | --------------------------------------------------------------------------------