├── .github └── workflows │ └── tests.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md └── src └── main.rs /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Check 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | check: 10 | name: Check 11 | runs-on: ubuntu-24.04 12 | steps: 13 | - name: Checkout sources 14 | uses: actions/checkout@v4 15 | 16 | - name: Install stable toolchain 17 | uses: actions-rs/toolchain@v1 18 | with: 19 | profile: minimal 20 | toolchain: stable 21 | override: true 22 | 23 | - name: Cache dependencies 24 | uses: Swatinem/rust-cache@v1 25 | 26 | - name: Run cargo check 27 | uses: actions-rs/cargo@v1 28 | with: 29 | command: check 30 | 31 | lints: 32 | name: Lints 33 | runs-on: ubuntu-24.04 34 | steps: 35 | - name: Checkout sources 36 | uses: actions/checkout@v4 37 | 38 | - name: Install stable toolchain 39 | uses: actions-rs/toolchain@v1 40 | with: 41 | profile: minimal 42 | toolchain: stable 43 | override: true 44 | components: rustfmt, clippy 45 | 46 | - name: Cache dependencies 47 | uses: Swatinem/rust-cache@v1 48 | 49 | - name: Run cargo fmt 50 | uses: actions-rs/cargo@v1 51 | with: 52 | command: fmt 53 | args: --all -- --check 54 | 55 | - name: Run cargo clippy 56 | uses: actions-rs/cargo@v1 57 | with: 58 | command: clippy 59 | args: -- -D warnings 60 | 61 | test: 62 | name: Test Suite 63 | runs-on: ${{ matrix.os }} 64 | strategy: 65 | matrix: 66 | os: [ubuntu-24.04, macOS-latest] 67 | steps: 68 | - name: Checkout sources 69 | uses: actions/checkout@v2 70 | 71 | - name: Install stable toolchain 72 | uses: actions-rs/toolchain@v1 73 | with: 74 | profile: minimal 75 | toolchain: stable 76 | override: true 77 | 78 | - name: Cache dependencies 79 | uses: Swatinem/rust-cache@v1 80 | 81 | - name: Run tests 82 | run: cargo test --verbose 83 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "anstream" 7 | version = "0.6.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 10 | dependencies = [ 11 | "anstyle", 12 | "anstyle-parse", 13 | "anstyle-query", 14 | "anstyle-wincon", 15 | "colorchoice", 16 | "is_terminal_polyfill", 17 | "utf8parse", 18 | ] 19 | 20 | [[package]] 21 | name = "anstyle" 22 | version = "1.0.10" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 25 | 26 | [[package]] 27 | name = "anstyle-parse" 28 | version = "0.2.6" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 31 | dependencies = [ 32 | "utf8parse", 33 | ] 34 | 35 | [[package]] 36 | name = "anstyle-query" 37 | version = "1.1.2" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 40 | dependencies = [ 41 | "windows-sys", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-wincon" 46 | version = "3.0.7" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" 49 | dependencies = [ 50 | "anstyle", 51 | "once_cell", 52 | "windows-sys", 53 | ] 54 | 55 | [[package]] 56 | name = "anyhow" 57 | version = "1.0.97" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" 60 | 61 | [[package]] 62 | name = "cargo-examples" 63 | version = "0.6.0" 64 | dependencies = [ 65 | "anyhow", 66 | "cargo_toml", 67 | "clap", 68 | "tap", 69 | "xshell", 70 | ] 71 | 72 | [[package]] 73 | name = "cargo_toml" 74 | version = "0.22.1" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "02260d489095346e5cafd04dea8e8cb54d1d74fcd759022a9b72986ebe9a1257" 77 | dependencies = [ 78 | "serde", 79 | "toml", 80 | ] 81 | 82 | [[package]] 83 | name = "clap" 84 | version = "4.5.32" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83" 87 | dependencies = [ 88 | "clap_builder", 89 | "clap_derive", 90 | ] 91 | 92 | [[package]] 93 | name = "clap_builder" 94 | version = "4.5.32" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8" 97 | dependencies = [ 98 | "anstream", 99 | "anstyle", 100 | "clap_lex", 101 | "strsim", 102 | ] 103 | 104 | [[package]] 105 | name = "clap_derive" 106 | version = "4.5.32" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" 109 | dependencies = [ 110 | "heck", 111 | "proc-macro2", 112 | "quote", 113 | "syn", 114 | ] 115 | 116 | [[package]] 117 | name = "clap_lex" 118 | version = "0.7.4" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 121 | 122 | [[package]] 123 | name = "colorchoice" 124 | version = "1.0.3" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 127 | 128 | [[package]] 129 | name = "equivalent" 130 | version = "1.0.2" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 133 | 134 | [[package]] 135 | name = "hashbrown" 136 | version = "0.15.2" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 139 | 140 | [[package]] 141 | name = "heck" 142 | version = "0.5.0" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 145 | 146 | [[package]] 147 | name = "indexmap" 148 | version = "2.8.0" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" 151 | dependencies = [ 152 | "equivalent", 153 | "hashbrown", 154 | ] 155 | 156 | [[package]] 157 | name = "is_terminal_polyfill" 158 | version = "1.70.1" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 161 | 162 | [[package]] 163 | name = "memchr" 164 | version = "2.7.4" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 167 | 168 | [[package]] 169 | name = "once_cell" 170 | version = "1.21.1" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" 173 | 174 | [[package]] 175 | name = "proc-macro2" 176 | version = "1.0.94" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" 179 | dependencies = [ 180 | "unicode-ident", 181 | ] 182 | 183 | [[package]] 184 | name = "quote" 185 | version = "1.0.40" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 188 | dependencies = [ 189 | "proc-macro2", 190 | ] 191 | 192 | [[package]] 193 | name = "serde" 194 | version = "1.0.219" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 197 | dependencies = [ 198 | "serde_derive", 199 | ] 200 | 201 | [[package]] 202 | name = "serde_derive" 203 | version = "1.0.219" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 206 | dependencies = [ 207 | "proc-macro2", 208 | "quote", 209 | "syn", 210 | ] 211 | 212 | [[package]] 213 | name = "serde_spanned" 214 | version = "0.6.8" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" 217 | dependencies = [ 218 | "serde", 219 | ] 220 | 221 | [[package]] 222 | name = "strsim" 223 | version = "0.11.1" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 226 | 227 | [[package]] 228 | name = "syn" 229 | version = "2.0.100" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" 232 | dependencies = [ 233 | "proc-macro2", 234 | "quote", 235 | "unicode-ident", 236 | ] 237 | 238 | [[package]] 239 | name = "tap" 240 | version = "1.0.1" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" 243 | 244 | [[package]] 245 | name = "toml" 246 | version = "0.8.20" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" 249 | dependencies = [ 250 | "serde", 251 | "serde_spanned", 252 | "toml_datetime", 253 | "toml_edit", 254 | ] 255 | 256 | [[package]] 257 | name = "toml_datetime" 258 | version = "0.6.8" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 261 | dependencies = [ 262 | "serde", 263 | ] 264 | 265 | [[package]] 266 | name = "toml_edit" 267 | version = "0.22.24" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" 270 | dependencies = [ 271 | "indexmap", 272 | "serde", 273 | "serde_spanned", 274 | "toml_datetime", 275 | "winnow", 276 | ] 277 | 278 | [[package]] 279 | name = "unicode-ident" 280 | version = "1.0.18" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 283 | 284 | [[package]] 285 | name = "utf8parse" 286 | version = "0.2.2" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 289 | 290 | [[package]] 291 | name = "windows-sys" 292 | version = "0.59.0" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 295 | dependencies = [ 296 | "windows-targets", 297 | ] 298 | 299 | [[package]] 300 | name = "windows-targets" 301 | version = "0.52.6" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 304 | dependencies = [ 305 | "windows_aarch64_gnullvm", 306 | "windows_aarch64_msvc", 307 | "windows_i686_gnu", 308 | "windows_i686_gnullvm", 309 | "windows_i686_msvc", 310 | "windows_x86_64_gnu", 311 | "windows_x86_64_gnullvm", 312 | "windows_x86_64_msvc", 313 | ] 314 | 315 | [[package]] 316 | name = "windows_aarch64_gnullvm" 317 | version = "0.52.6" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 320 | 321 | [[package]] 322 | name = "windows_aarch64_msvc" 323 | version = "0.52.6" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 326 | 327 | [[package]] 328 | name = "windows_i686_gnu" 329 | version = "0.52.6" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 332 | 333 | [[package]] 334 | name = "windows_i686_gnullvm" 335 | version = "0.52.6" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 338 | 339 | [[package]] 340 | name = "windows_i686_msvc" 341 | version = "0.52.6" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 344 | 345 | [[package]] 346 | name = "windows_x86_64_gnu" 347 | version = "0.52.6" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 350 | 351 | [[package]] 352 | name = "windows_x86_64_gnullvm" 353 | version = "0.52.6" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 356 | 357 | [[package]] 358 | name = "windows_x86_64_msvc" 359 | version = "0.52.6" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 362 | 363 | [[package]] 364 | name = "winnow" 365 | version = "0.7.4" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36" 368 | dependencies = [ 369 | "memchr", 370 | ] 371 | 372 | [[package]] 373 | name = "xshell" 374 | version = "0.2.7" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "9e7290c623014758632efe00737145b6867b66292c42167f2ec381eb566a373d" 377 | dependencies = [ 378 | "xshell-macros", 379 | ] 380 | 381 | [[package]] 382 | name = "xshell-macros" 383 | version = "0.2.7" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "32ac00cd3f8ec9c1d33fb3e7958a82df6989c42d747bd326c822b1d625283547" 386 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cargo-examples" 3 | version = "0.6.0" 4 | authors = ["Richard Hozák"] 5 | edition = "2024" 6 | license = "MIT OR Apache-2.0" 7 | readme = "README.md" 8 | homepage = "https://github.com/zxey/cargo-examples" 9 | description = "Run all examples for any locally cloned crate" 10 | categories = [ 11 | "command-line-utilities", 12 | "development-tools::cargo-plugins", 13 | ] 14 | keywords = [ 15 | "cargo", 16 | "cargo-subcommand", 17 | "cli", 18 | "examples", 19 | "crates", 20 | ] 21 | 22 | [dependencies] 23 | anyhow = "1.0.97" 24 | clap = { version = "4.5", features = ["derive"] } 25 | tap = "1.0.1" 26 | xshell = "0.2.7" 27 | cargo_toml = "0.22.1" 28 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT/X Consortium License 2 | 3 | @ 2019-2021 Fedor Logachev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the "Software"), 7 | to deal in the Software without restriction, including without limitation 8 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | and/or sell copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all 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 18 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | DEALINGS IN THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cargo-examples 2 | 3 | `cargo-examples` is a cargo subcommand that lets you run all examples in any locally cloned crate. 4 | 5 | My main motivation for this tool is that when checking out a library or binary, I want to run all the examples so I can quickly grasp the inner workings of a crate interactively. 6 | 7 | This tool supports running examples the same way `cargo run --example ` does, meaning it can run single files from `examples` directory, [multi-file](https://doc.rust-lang.org/cargo/guide/project-layout.html) examples in `examples` directory and [manifest-based](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#examples) example specified in projects `Cargo.toml`. 8 | 9 | In addition to this `cargo-examples` allows you to run subproject examples from `examples` directory, meaning subproject directories with `Cargo.toml` that sit in `examples` directory, this can be seen in many projects across Rust ecosystem which have more involved examples, cargo cannot run this out-of-the box with `cargo run --example `. 10 | 11 | > **Single file example** 12 | > `/examples/foo.rs` 13 | 14 | > **Multi file example** 15 | > `/examples/bar/main.rs` 16 | 17 | > **Subproject example** 18 | > `/examples/baz/Cargo.toml` 19 | 20 | > **Manifest based example** 21 | > `[[example]]` in `Cargo.toml` 22 | 23 | ## Installing 24 | 25 | You can install this with cargo: 26 | ``` 27 | cargo install cargo-examples 28 | ``` 29 | 30 | ## Usage 31 | 32 | Clone your favorite crate, `cd` into the repo and run `cargo examples` 33 | 34 | By default, `cargo examples` will run all the examples in a crate in order. 35 | 36 | There are cli options that you can use to modify how the examples are ran: 37 | 38 | ``` 39 | Cargo subcommand to run all examples for any locally cloned crate 40 | USAGE: 41 | cargo examples [OPTIONS] [-- ...] 42 | ARGUMENTS: 43 | [CARGO_ARGS]... Pass these arguments along to cargo when running 44 | OPTIONS: 45 | --manifest-path Path to Cargo.toml 46 | -l, --list List *all* examples and print them out before running any 47 | -p, --print Print example name before running 48 | -f, --from Run example starting with 49 | -n, --no-run Do not run any examples, useful when combined with `--list`, or `--from` + `--print` 50 | -s, --skip Skip when running. (--skip=example1,example2) 51 | -F, --features Run examples with enabled. (--features=feature1,feature2) 52 | -h, --help Print help 53 | -V, --version Print version 54 | ``` 55 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::{OsStr, OsString}, 3 | fs, 4 | path::PathBuf, 5 | str::FromStr, 6 | }; 7 | 8 | use anyhow::{Context, anyhow}; 9 | use cargo_toml::Manifest; 10 | use clap::Parser; 11 | use tap::Tap; 12 | use xshell::{Shell, cmd}; 13 | 14 | #[derive(Parser)] // requires `derive` feature 15 | #[clap(name = "cargo")] 16 | #[clap(bin_name = "cargo")] 17 | enum Cargo { 18 | Examples(Examples), 19 | } 20 | 21 | /// Cargo subcommand to run all examples for any locally cloned crate 22 | #[derive(Parser)] 23 | #[clap(version, about, long_about = None, args_conflicts_with_subcommands = true)] 24 | struct Examples { 25 | /// Path to Cargo.toml 26 | #[clap(long, value_parser, value_name = "FILE")] 27 | manifest_path: Option, 28 | 29 | /// List *all* examples and print them out before running any 30 | #[clap(short, long)] 31 | list: bool, 32 | 33 | /// Print example name before running 34 | #[clap(short, long)] 35 | print: bool, 36 | 37 | /// Run example starting with 38 | #[clap(short, long, value_name = "EXAMPLE")] 39 | from: Option, 40 | 41 | /// Do not run any examples, useful when combined with `--list`, or `--from` + `--print` 42 | #[clap(short, long)] 43 | no_run: bool, 44 | 45 | /// Skip when running. (--skip=example1,example2) 46 | #[clap( 47 | short, 48 | long, 49 | value_parser, 50 | use_value_delimiter = true, 51 | value_name = "EXAMPLE" 52 | )] 53 | skip: Vec, 54 | 55 | /// Run examples with enabled. (--features=feature1,feature2) 56 | #[clap( 57 | short = 'F', 58 | long, 59 | value_parser, 60 | use_value_delimiter = true, 61 | value_name = "FEATURES" 62 | )] 63 | features: Vec, 64 | 65 | /// Pass these arguments along to cargo when running 66 | #[clap(raw = true)] 67 | cargo_args: Option, 68 | } 69 | 70 | enum Example { 71 | File(PathBuf), 72 | MultiFile(PathBuf), 73 | SubProject(PathBuf), 74 | Named(OsString), 75 | } 76 | 77 | impl Example { 78 | fn name(&self) -> Option<&OsStr> { 79 | match self { 80 | Example::File(path) => Some(path.file_stem()?), 81 | Example::SubProject(path) | Example::MultiFile(path) => { 82 | Some(path.parent()?.file_name()?) 83 | } 84 | Example::Named(name) => Some(name.as_os_str()), 85 | } 86 | } 87 | } 88 | 89 | #[allow(clippy::too_many_lines)] 90 | fn main() -> anyhow::Result<()> { 91 | let Cargo::Examples(cli) = Cargo::parse(); 92 | let sh = Shell::new()?; 93 | 94 | let manifest_path = cli 95 | .manifest_path 96 | .unwrap_or(PathBuf::from_str("Cargo.toml").unwrap()); 97 | 98 | if !manifest_path.is_file() { 99 | return Err(anyhow!( 100 | "the manifest-path must be a path to a Cargo.toml file" 101 | )); 102 | } 103 | 104 | let root_dir = manifest_path 105 | .parent() 106 | .context("Cargo.toml does not have parent directory")?; 107 | 108 | let examples_dir = root_dir.to_path_buf().tap_mut(|p| p.push("examples")); 109 | 110 | // examples can be: 111 | // - /examples/example_foo.rs 112 | // - in this case the example can be called by `cargo run --example example_foo 113 | // - /examples/example_bar/main.rs 114 | // - in this case the example can be called by `cargo run --example example_bar 115 | // - /examples/example_baz/Cargo.toml 116 | // - this is not really an example but more of a project directory in 117 | // examples directory cargo does not recognize this as an example, but a 118 | // lot of projects use this for more involved examples 119 | // - this can be ran as `cargo run --manifest-path examples/example_baz/Cargo.toml` 120 | 121 | let mut examples: Vec = fs::read_dir(examples_dir)? 122 | .filter_map(std::result::Result::ok) // ignore entries with errors 123 | .map(|entry| entry.path()) 124 | .filter_map(|path| { 125 | if path.is_file() { 126 | // filter out files with .rs extension 127 | if path.extension().is_some_and(|ext| ext == "rs") { 128 | // take the file name without extension 129 | return Some(Example::File(path)); 130 | } 131 | } else if path.is_dir() { 132 | // filter out directories without main.rs or Cargo.toml inside them 133 | 134 | let example_main_path = path.clone().tap_mut(|p| p.push("main.rs")); 135 | if example_main_path.is_file() { 136 | // return the name of directory 137 | return Some(Example::MultiFile(example_main_path)); 138 | } 139 | 140 | let example_manifest_path = path.clone().tap_mut(|p| p.push("Cargo.toml")); 141 | if example_manifest_path.is_file() { 142 | // return the path to manifest file 143 | return Some(Example::SubProject(example_manifest_path)); 144 | } 145 | } 146 | 147 | None 148 | }) 149 | .filter(|example| example.name().is_some()) 150 | .collect::>() 151 | .tap_mut(|examples| examples.sort_by(|a, b| a.name().unwrap().cmp(b.name().unwrap()))); // sort the files, so output and execution is deterministic when using `from` 152 | 153 | // Examples can also exist in an arbitrary path defined in an [[example]] block in the project's manifest 154 | 155 | let manifest = Manifest::from_path(manifest_path.clone())?; 156 | for name in manifest 157 | .example 158 | .iter() 159 | .filter_map(|product| product.name.clone()) 160 | { 161 | examples.push(Example::Named(name.into())); 162 | } 163 | 164 | if cli.list { 165 | // print all examples, unfiltered 166 | for example in &examples { 167 | println!("{}", example.name().unwrap().to_string_lossy()); 168 | } 169 | } 170 | 171 | // if `from` is not specified run all examples 172 | let mut run_examples = cli.from.is_none(); 173 | 174 | // if 'features' is specified pass them allong to cargo 175 | let features = if cli.features.is_empty() { 176 | String::new() 177 | } else { 178 | let mut f = "--features=".to_owned(); 179 | f.push_str(cli.features.join(",").as_str()); 180 | f 181 | }; 182 | 183 | for example in &examples { 184 | if let Some(ref from) = cli.from { 185 | // execute only example starting with `from`, if `from` is specified 186 | if from == example.name().unwrap() { 187 | run_examples = true; 188 | } 189 | } 190 | 191 | // skip any examples specified in the `skip` arg 192 | if cli.skip.iter().any(|s| *s == example.name().unwrap()) { 193 | continue; 194 | } 195 | 196 | if !run_examples { 197 | continue; 198 | } 199 | 200 | if cli.print { 201 | println!("{}", example.name().unwrap().to_string_lossy()); 202 | } 203 | 204 | if cli.no_run { 205 | continue; 206 | } 207 | 208 | sh.change_dir(root_dir); 209 | 210 | let command = match example { 211 | Example::File(_) | Example::Named(_) => { 212 | let name = example.name().unwrap(); 213 | cmd!( 214 | sh, 215 | "cargo run --manifest-path {manifest_path} --example {name} {features}" 216 | ) 217 | } 218 | Example::MultiFile(_) => { 219 | let name = example.name().unwrap(); 220 | cmd!( 221 | sh, 222 | "cargo run --manifest-path {manifest_path} --example {name} {features}" 223 | ) 224 | } 225 | Example::SubProject(manifest_path) => { 226 | cmd!(sh, "cargo run --manifest-path {manifest_path} {features}") 227 | } 228 | }; 229 | 230 | if let Some(ref args) = cli.cargo_args { 231 | command.arg(args).run()?; 232 | } else { 233 | command.run()?; 234 | } 235 | } 236 | 237 | Ok(()) 238 | } 239 | --------------------------------------------------------------------------------