├── .github └── workflows │ ├── audit.yml │ ├── ci.yml │ └── nightly.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── count_1 ├── Cargo.toml └── src │ └── main.rs ├── count_2 ├── Cargo.toml └── src │ ├── lib.rs │ └── main.rs ├── count_3 ├── Cargo.toml └── src │ ├── lib.rs │ └── main.rs ├── count_4 ├── Cargo.toml └── src │ ├── lib.rs │ └── main.rs ├── count_5 ├── Cargo.toml └── src │ ├── lib.rs │ └── main.rs ├── count_6 ├── Cargo.toml └── src │ ├── lib.rs │ └── main.rs ├── count_7 ├── Cargo.toml ├── src │ ├── lib.rs │ └── main.rs └── tests │ └── data │ └── test.txt ├── count_8 ├── Cargo.toml ├── src │ ├── lib.rs │ └── main.rs └── tests │ ├── data │ ├── t2.txt │ └── test.txt │ └── integration.rs ├── count_9 ├── Cargo.toml ├── src │ ├── lib.rs │ └── main.rs └── tests │ ├── data │ ├── test.txt │ └── test2.txt │ └── integration.rs ├── cover_small.png ├── deny.toml ├── hello_1 ├── Cargo.toml └── src │ └── main.rs ├── hello_2 ├── Cargo.toml └── src │ ├── lib.rs │ └── main.rs ├── hello_3 ├── Cargo.toml └── src │ ├── lib.rs │ └── main.rs ├── logbook_1 ├── Cargo.toml └── src │ └── main.rs ├── logbook_2 ├── Cargo.toml └── src │ └── main.rs ├── logbook_3 ├── Cargo.toml └── src │ └── main.rs ├── logbook_4 ├── Cargo.toml ├── README.md ├── src │ ├── lib.rs │ └── main.rs └── tests │ └── data │ ├── empty.txt │ └── logbook.txt ├── memo_1 ├── Cargo.toml ├── src │ ├── lib.rs │ └── main.rs └── tests │ └── data │ └── memos.txt ├── memo_2 ├── Cargo.toml ├── src │ ├── lib.rs │ └── main.rs └── tests │ └── data │ └── memos.txt ├── memo_3 ├── Cargo.toml └── src │ ├── lib.rs │ └── main.rs ├── memo_4 ├── Cargo.toml └── src │ ├── lib.rs │ └── main.rs ├── memo_5 ├── Cargo.toml └── src │ ├── lib.rs │ └── main.rs ├── rustfmt.toml ├── slim_1 ├── Cargo.toml ├── src │ ├── lib.rs │ └── main.rs └── tests │ └── data │ ├── proj_1 │ ├── Cargo.toml │ └── src │ │ └── main.rs │ ├── proj_2 │ ├── Cargo.toml │ └── src │ │ └── main.rs │ └── proj_3 │ ├── Cargo.toml │ └── src │ └── main.rs ├── slim_2 ├── Cargo.toml ├── src │ ├── lib.rs │ └── main.rs └── tests │ └── data │ ├── proj_1 │ ├── Cargo.toml │ └── src │ │ └── main.rs │ ├── proj_2 │ ├── Cargo.toml │ └── src │ │ └── main.rs │ └── proj_3 │ ├── Cargo.toml │ └── src │ └── main.rs ├── slim_3 ├── Cargo.toml ├── src │ ├── lib.rs │ └── main.rs └── tests │ └── data │ ├── proj_1 │ ├── Cargo.toml │ ├── src │ │ └── main.rs │ └── target │ │ └── package │ │ └── Cargo.toml │ ├── proj_2 │ ├── Cargo.toml │ └── src │ │ └── main.rs │ └── proj_3 │ ├── Cargo.toml │ └── src │ └── main.rs ├── timer_1 ├── Cargo.toml └── src │ └── main.rs ├── timer_2 ├── Cargo.toml └── src │ ├── lib.rs │ └── main.rs ├── weather_1 ├── Cargo.toml └── src │ └── main.rs ├── weather_2 ├── Cargo.toml ├── src │ ├── lib.rs │ └── main.rs └── tests │ └── data │ └── ws.json ├── weather_3 ├── Cargo.toml ├── src │ ├── lib.rs │ └── main.rs └── tests │ └── data │ └── ws.json └── weather_4 ├── Cargo.toml ├── src ├── lib.rs └── main.rs └── tests └── data └── ws.json /.github/workflows/audit.yml: -------------------------------------------------------------------------------- 1 | name: Security audit 2 | on: 3 | schedule: 4 | - cron: "0 0 * * *" 5 | push: 6 | paths: 7 | - "**/Cargo.toml" 8 | - "**/Cargo.lock" 9 | workflow_dispatch: 10 | jobs: 11 | security_audit: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: EmbarkStudios/cargo-deny-action@v2 16 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request, workflow_dispatch] 3 | env: 4 | CARGO_TERM_COLOR: always 5 | 6 | jobs: 7 | lint: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | - uses: dtolnay/rust-toolchain@stable 12 | with: 13 | components: "clippy, rustfmt" 14 | - uses: Swatinem/rust-cache@v2 15 | - run: cargo fmt --all -- --check 16 | - run: cargo clippy --all-targets --all-features -- -D clippy::pedantic -D warnings 17 | 18 | test: 19 | strategy: 20 | matrix: 21 | os: [ubuntu-latest, macos-latest, windows-latest] 22 | runs-on: ${{ matrix.os }} 23 | steps: 24 | - uses: actions/checkout@v4 25 | - uses: dtolnay/rust-toolchain@stable 26 | - uses: Swatinem/rust-cache@v2 27 | - run: cargo test --all-features 28 | -------------------------------------------------------------------------------- /.github/workflows/nightly.yml: -------------------------------------------------------------------------------- 1 | name: Nightly 2 | on: [pull_request, workflow_dispatch] 3 | env: 4 | CARGO_TERM_COLOR: always 5 | 6 | jobs: 7 | test: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | - uses: dtolnay/rust-toolchain@nightly 12 | - uses: Swatinem/rust-cache@v2 13 | - run: cargo test --all-features 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /target 3 | logbook.txt 4 | !**/data/logbook.txt -------------------------------------------------------------------------------- /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 = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "anstream" 31 | version = "0.6.18" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 34 | dependencies = [ 35 | "anstyle", 36 | "anstyle-parse", 37 | "anstyle-query", 38 | "anstyle-wincon", 39 | "colorchoice", 40 | "is_terminal_polyfill", 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle" 46 | version = "1.0.10" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 49 | 50 | [[package]] 51 | name = "anstyle-parse" 52 | version = "0.2.6" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 55 | dependencies = [ 56 | "utf8parse", 57 | ] 58 | 59 | [[package]] 60 | name = "anstyle-query" 61 | version = "1.1.2" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 64 | dependencies = [ 65 | "windows-sys 0.59.0", 66 | ] 67 | 68 | [[package]] 69 | name = "anstyle-wincon" 70 | version = "3.0.7" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" 73 | dependencies = [ 74 | "anstyle", 75 | "once_cell", 76 | "windows-sys 0.59.0", 77 | ] 78 | 79 | [[package]] 80 | name = "anyhow" 81 | version = "1.0.97" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" 84 | 85 | [[package]] 86 | name = "ascii-canvas" 87 | version = "3.0.0" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" 90 | dependencies = [ 91 | "term", 92 | ] 93 | 94 | [[package]] 95 | name = "assert-json-diff" 96 | version = "2.0.2" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" 99 | dependencies = [ 100 | "serde", 101 | "serde_json", 102 | ] 103 | 104 | [[package]] 105 | name = "assert_cmd" 106 | version = "2.0.16" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d" 109 | dependencies = [ 110 | "anstyle", 111 | "bstr", 112 | "doc-comment", 113 | "libc", 114 | "predicates", 115 | "predicates-core", 116 | "predicates-tree", 117 | "wait-timeout", 118 | ] 119 | 120 | [[package]] 121 | name = "async-attributes" 122 | version = "1.1.2" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" 125 | dependencies = [ 126 | "quote", 127 | "syn 1.0.109", 128 | ] 129 | 130 | [[package]] 131 | name = "async-channel" 132 | version = "1.9.0" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" 135 | dependencies = [ 136 | "concurrent-queue", 137 | "event-listener 2.5.3", 138 | "futures-core", 139 | ] 140 | 141 | [[package]] 142 | name = "async-channel" 143 | version = "2.3.1" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" 146 | dependencies = [ 147 | "concurrent-queue", 148 | "event-listener-strategy", 149 | "futures-core", 150 | "pin-project-lite", 151 | ] 152 | 153 | [[package]] 154 | name = "async-executor" 155 | version = "1.13.1" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" 158 | dependencies = [ 159 | "async-task", 160 | "concurrent-queue", 161 | "fastrand", 162 | "futures-lite", 163 | "slab", 164 | ] 165 | 166 | [[package]] 167 | name = "async-global-executor" 168 | version = "2.4.1" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" 171 | dependencies = [ 172 | "async-channel 2.3.1", 173 | "async-executor", 174 | "async-io", 175 | "async-lock", 176 | "blocking", 177 | "futures-lite", 178 | "once_cell", 179 | ] 180 | 181 | [[package]] 182 | name = "async-io" 183 | version = "2.4.0" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" 186 | dependencies = [ 187 | "async-lock", 188 | "cfg-if", 189 | "concurrent-queue", 190 | "futures-io", 191 | "futures-lite", 192 | "parking", 193 | "polling", 194 | "rustix 0.38.44", 195 | "slab", 196 | "tracing", 197 | "windows-sys 0.59.0", 198 | ] 199 | 200 | [[package]] 201 | name = "async-lock" 202 | version = "3.4.0" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" 205 | dependencies = [ 206 | "event-listener 5.4.0", 207 | "event-listener-strategy", 208 | "pin-project-lite", 209 | ] 210 | 211 | [[package]] 212 | name = "async-object-pool" 213 | version = "0.1.5" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "333c456b97c3f2d50604e8b2624253b7f787208cb72eb75e64b0ad11b221652c" 216 | dependencies = [ 217 | "async-std", 218 | ] 219 | 220 | [[package]] 221 | name = "async-process" 222 | version = "2.3.0" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" 225 | dependencies = [ 226 | "async-channel 2.3.1", 227 | "async-io", 228 | "async-lock", 229 | "async-signal", 230 | "async-task", 231 | "blocking", 232 | "cfg-if", 233 | "event-listener 5.4.0", 234 | "futures-lite", 235 | "rustix 0.38.44", 236 | "tracing", 237 | ] 238 | 239 | [[package]] 240 | name = "async-signal" 241 | version = "0.2.10" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" 244 | dependencies = [ 245 | "async-io", 246 | "async-lock", 247 | "atomic-waker", 248 | "cfg-if", 249 | "futures-core", 250 | "futures-io", 251 | "rustix 0.38.44", 252 | "signal-hook-registry", 253 | "slab", 254 | "windows-sys 0.59.0", 255 | ] 256 | 257 | [[package]] 258 | name = "async-std" 259 | version = "1.13.1" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "730294c1c08c2e0f85759590518f6333f0d5a0a766a27d519c1b244c3dfd8a24" 262 | dependencies = [ 263 | "async-attributes", 264 | "async-channel 1.9.0", 265 | "async-global-executor", 266 | "async-io", 267 | "async-lock", 268 | "async-process", 269 | "crossbeam-utils", 270 | "futures-channel", 271 | "futures-core", 272 | "futures-io", 273 | "futures-lite", 274 | "gloo-timers", 275 | "kv-log-macro", 276 | "log", 277 | "memchr", 278 | "once_cell", 279 | "pin-project-lite", 280 | "pin-utils", 281 | "slab", 282 | "wasm-bindgen-futures", 283 | ] 284 | 285 | [[package]] 286 | name = "async-task" 287 | version = "4.7.1" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" 290 | 291 | [[package]] 292 | name = "async-trait" 293 | version = "0.1.88" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" 296 | dependencies = [ 297 | "proc-macro2", 298 | "quote", 299 | "syn 2.0.100", 300 | ] 301 | 302 | [[package]] 303 | name = "atomic-waker" 304 | version = "1.1.2" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 307 | 308 | [[package]] 309 | name = "autocfg" 310 | version = "1.4.0" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 313 | 314 | [[package]] 315 | name = "backtrace" 316 | version = "0.3.74" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 319 | dependencies = [ 320 | "addr2line", 321 | "cfg-if", 322 | "libc", 323 | "miniz_oxide", 324 | "object", 325 | "rustc-demangle", 326 | "windows-targets 0.52.6", 327 | ] 328 | 329 | [[package]] 330 | name = "base64" 331 | version = "0.21.7" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 334 | 335 | [[package]] 336 | name = "base64" 337 | version = "0.22.1" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 340 | 341 | [[package]] 342 | name = "basic-cookies" 343 | version = "0.1.5" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "67bd8fd42c16bdb08688243dc5f0cc117a3ca9efeeaba3a345a18a6159ad96f7" 346 | dependencies = [ 347 | "lalrpop", 348 | "lalrpop-util", 349 | "regex", 350 | ] 351 | 352 | [[package]] 353 | name = "bit-set" 354 | version = "0.5.3" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" 357 | dependencies = [ 358 | "bit-vec", 359 | ] 360 | 361 | [[package]] 362 | name = "bit-vec" 363 | version = "0.6.3" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" 366 | 367 | [[package]] 368 | name = "bitflags" 369 | version = "2.9.0" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 372 | 373 | [[package]] 374 | name = "blocking" 375 | version = "1.6.1" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" 378 | dependencies = [ 379 | "async-channel 2.3.1", 380 | "async-task", 381 | "futures-io", 382 | "futures-lite", 383 | "piper", 384 | ] 385 | 386 | [[package]] 387 | name = "bstr" 388 | version = "1.11.3" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" 391 | dependencies = [ 392 | "memchr", 393 | "regex-automata", 394 | "serde", 395 | ] 396 | 397 | [[package]] 398 | name = "bumpalo" 399 | version = "3.17.0" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 402 | 403 | [[package]] 404 | name = "bytes" 405 | version = "1.10.1" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 408 | 409 | [[package]] 410 | name = "cc" 411 | version = "1.2.18" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c" 414 | dependencies = [ 415 | "shlex", 416 | ] 417 | 418 | [[package]] 419 | name = "cfg-if" 420 | version = "1.0.0" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 423 | 424 | [[package]] 425 | name = "clap" 426 | version = "4.5.35" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944" 429 | dependencies = [ 430 | "clap_builder", 431 | "clap_derive", 432 | ] 433 | 434 | [[package]] 435 | name = "clap_builder" 436 | version = "4.5.35" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9" 439 | dependencies = [ 440 | "anstream", 441 | "anstyle", 442 | "clap_lex", 443 | "strsim", 444 | ] 445 | 446 | [[package]] 447 | name = "clap_derive" 448 | version = "4.5.32" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" 451 | dependencies = [ 452 | "heck", 453 | "proc-macro2", 454 | "quote", 455 | "syn 2.0.100", 456 | ] 457 | 458 | [[package]] 459 | name = "clap_lex" 460 | version = "0.7.4" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 463 | 464 | [[package]] 465 | name = "colorchoice" 466 | version = "1.0.3" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 469 | 470 | [[package]] 471 | name = "concurrent-queue" 472 | version = "2.5.0" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" 475 | dependencies = [ 476 | "crossbeam-utils", 477 | ] 478 | 479 | [[package]] 480 | name = "core-foundation" 481 | version = "0.9.4" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 484 | dependencies = [ 485 | "core-foundation-sys", 486 | "libc", 487 | ] 488 | 489 | [[package]] 490 | name = "core-foundation-sys" 491 | version = "0.8.7" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 494 | 495 | [[package]] 496 | name = "count_1" 497 | version = "0.1.0" 498 | 499 | [[package]] 500 | name = "count_2" 501 | version = "0.1.0" 502 | 503 | [[package]] 504 | name = "count_3" 505 | version = "0.1.0" 506 | 507 | [[package]] 508 | name = "count_4" 509 | version = "0.1.0" 510 | 511 | [[package]] 512 | name = "count_5" 513 | version = "0.1.0" 514 | dependencies = [ 515 | "anyhow", 516 | ] 517 | 518 | [[package]] 519 | name = "count_6" 520 | version = "0.1.0" 521 | dependencies = [ 522 | "anyhow", 523 | ] 524 | 525 | [[package]] 526 | name = "count_7" 527 | version = "0.1.0" 528 | dependencies = [ 529 | "anyhow", 530 | ] 531 | 532 | [[package]] 533 | name = "count_8" 534 | version = "0.1.0" 535 | dependencies = [ 536 | "anyhow", 537 | "assert_cmd", 538 | "predicates", 539 | ] 540 | 541 | [[package]] 542 | name = "count_9" 543 | version = "0.1.0" 544 | dependencies = [ 545 | "anyhow", 546 | "assert_cmd", 547 | "clap", 548 | "predicates", 549 | ] 550 | 551 | [[package]] 552 | name = "crossbeam-utils" 553 | version = "0.8.21" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 556 | 557 | [[package]] 558 | name = "crunchy" 559 | version = "0.2.3" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" 562 | 563 | [[package]] 564 | name = "difflib" 565 | version = "0.4.0" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" 568 | 569 | [[package]] 570 | name = "dirs-next" 571 | version = "2.0.0" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" 574 | dependencies = [ 575 | "cfg-if", 576 | "dirs-sys-next", 577 | ] 578 | 579 | [[package]] 580 | name = "dirs-sys-next" 581 | version = "0.1.2" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" 584 | dependencies = [ 585 | "libc", 586 | "redox_users", 587 | "winapi", 588 | ] 589 | 590 | [[package]] 591 | name = "displaydoc" 592 | version = "0.2.5" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 595 | dependencies = [ 596 | "proc-macro2", 597 | "quote", 598 | "syn 2.0.100", 599 | ] 600 | 601 | [[package]] 602 | name = "doc-comment" 603 | version = "0.3.3" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" 606 | 607 | [[package]] 608 | name = "either" 609 | version = "1.15.0" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 612 | 613 | [[package]] 614 | name = "ena" 615 | version = "0.14.3" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" 618 | dependencies = [ 619 | "log", 620 | ] 621 | 622 | [[package]] 623 | name = "encoding_rs" 624 | version = "0.8.35" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 627 | dependencies = [ 628 | "cfg-if", 629 | ] 630 | 631 | [[package]] 632 | name = "equivalent" 633 | version = "1.0.2" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 636 | 637 | [[package]] 638 | name = "errno" 639 | version = "0.3.11" 640 | source = "registry+https://github.com/rust-lang/crates.io-index" 641 | checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" 642 | dependencies = [ 643 | "libc", 644 | "windows-sys 0.59.0", 645 | ] 646 | 647 | [[package]] 648 | name = "event-listener" 649 | version = "2.5.3" 650 | source = "registry+https://github.com/rust-lang/crates.io-index" 651 | checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" 652 | 653 | [[package]] 654 | name = "event-listener" 655 | version = "5.4.0" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" 658 | dependencies = [ 659 | "concurrent-queue", 660 | "parking", 661 | "pin-project-lite", 662 | ] 663 | 664 | [[package]] 665 | name = "event-listener-strategy" 666 | version = "0.5.4" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" 669 | dependencies = [ 670 | "event-listener 5.4.0", 671 | "pin-project-lite", 672 | ] 673 | 674 | [[package]] 675 | name = "fastrand" 676 | version = "2.3.0" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 679 | 680 | [[package]] 681 | name = "fixedbitset" 682 | version = "0.4.2" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" 685 | 686 | [[package]] 687 | name = "float-cmp" 688 | version = "0.10.0" 689 | source = "registry+https://github.com/rust-lang/crates.io-index" 690 | checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" 691 | dependencies = [ 692 | "num-traits", 693 | ] 694 | 695 | [[package]] 696 | name = "fnv" 697 | version = "1.0.7" 698 | source = "registry+https://github.com/rust-lang/crates.io-index" 699 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 700 | 701 | [[package]] 702 | name = "foreign-types" 703 | version = "0.3.2" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 706 | dependencies = [ 707 | "foreign-types-shared", 708 | ] 709 | 710 | [[package]] 711 | name = "foreign-types-shared" 712 | version = "0.1.1" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 715 | 716 | [[package]] 717 | name = "form_urlencoded" 718 | version = "1.2.1" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 721 | dependencies = [ 722 | "percent-encoding", 723 | ] 724 | 725 | [[package]] 726 | name = "futures-channel" 727 | version = "0.3.31" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 730 | dependencies = [ 731 | "futures-core", 732 | "futures-sink", 733 | ] 734 | 735 | [[package]] 736 | name = "futures-core" 737 | version = "0.3.31" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 740 | 741 | [[package]] 742 | name = "futures-io" 743 | version = "0.3.31" 744 | source = "registry+https://github.com/rust-lang/crates.io-index" 745 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 746 | 747 | [[package]] 748 | name = "futures-lite" 749 | version = "2.6.0" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" 752 | dependencies = [ 753 | "fastrand", 754 | "futures-core", 755 | "futures-io", 756 | "parking", 757 | "pin-project-lite", 758 | ] 759 | 760 | [[package]] 761 | name = "futures-macro" 762 | version = "0.3.31" 763 | source = "registry+https://github.com/rust-lang/crates.io-index" 764 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 765 | dependencies = [ 766 | "proc-macro2", 767 | "quote", 768 | "syn 2.0.100", 769 | ] 770 | 771 | [[package]] 772 | name = "futures-sink" 773 | version = "0.3.31" 774 | source = "registry+https://github.com/rust-lang/crates.io-index" 775 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 776 | 777 | [[package]] 778 | name = "futures-task" 779 | version = "0.3.31" 780 | source = "registry+https://github.com/rust-lang/crates.io-index" 781 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 782 | 783 | [[package]] 784 | name = "futures-util" 785 | version = "0.3.31" 786 | source = "registry+https://github.com/rust-lang/crates.io-index" 787 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 788 | dependencies = [ 789 | "futures-core", 790 | "futures-io", 791 | "futures-macro", 792 | "futures-sink", 793 | "futures-task", 794 | "memchr", 795 | "pin-project-lite", 796 | "pin-utils", 797 | "slab", 798 | ] 799 | 800 | [[package]] 801 | name = "getrandom" 802 | version = "0.2.15" 803 | source = "registry+https://github.com/rust-lang/crates.io-index" 804 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 805 | dependencies = [ 806 | "cfg-if", 807 | "libc", 808 | "wasi 0.11.0+wasi-snapshot-preview1", 809 | ] 810 | 811 | [[package]] 812 | name = "getrandom" 813 | version = "0.3.2" 814 | source = "registry+https://github.com/rust-lang/crates.io-index" 815 | checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" 816 | dependencies = [ 817 | "cfg-if", 818 | "libc", 819 | "r-efi", 820 | "wasi 0.14.2+wasi-0.2.4", 821 | ] 822 | 823 | [[package]] 824 | name = "gimli" 825 | version = "0.31.1" 826 | source = "registry+https://github.com/rust-lang/crates.io-index" 827 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 828 | 829 | [[package]] 830 | name = "gloo-timers" 831 | version = "0.3.0" 832 | source = "registry+https://github.com/rust-lang/crates.io-index" 833 | checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" 834 | dependencies = [ 835 | "futures-channel", 836 | "futures-core", 837 | "js-sys", 838 | "wasm-bindgen", 839 | ] 840 | 841 | [[package]] 842 | name = "h2" 843 | version = "0.4.8" 844 | source = "registry+https://github.com/rust-lang/crates.io-index" 845 | checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" 846 | dependencies = [ 847 | "atomic-waker", 848 | "bytes", 849 | "fnv", 850 | "futures-core", 851 | "futures-sink", 852 | "http 1.3.1", 853 | "indexmap", 854 | "slab", 855 | "tokio", 856 | "tokio-util", 857 | "tracing", 858 | ] 859 | 860 | [[package]] 861 | name = "hashbrown" 862 | version = "0.15.2" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 865 | 866 | [[package]] 867 | name = "heck" 868 | version = "0.5.0" 869 | source = "registry+https://github.com/rust-lang/crates.io-index" 870 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 871 | 872 | [[package]] 873 | name = "hello_1" 874 | version = "0.1.0" 875 | 876 | [[package]] 877 | name = "hello_2" 878 | version = "0.1.0" 879 | 880 | [[package]] 881 | name = "hello_3" 882 | version = "0.1.0" 883 | 884 | [[package]] 885 | name = "hermit-abi" 886 | version = "0.4.0" 887 | source = "registry+https://github.com/rust-lang/crates.io-index" 888 | checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" 889 | 890 | [[package]] 891 | name = "http" 892 | version = "0.2.12" 893 | source = "registry+https://github.com/rust-lang/crates.io-index" 894 | checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" 895 | dependencies = [ 896 | "bytes", 897 | "fnv", 898 | "itoa", 899 | ] 900 | 901 | [[package]] 902 | name = "http" 903 | version = "1.3.1" 904 | source = "registry+https://github.com/rust-lang/crates.io-index" 905 | checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" 906 | dependencies = [ 907 | "bytes", 908 | "fnv", 909 | "itoa", 910 | ] 911 | 912 | [[package]] 913 | name = "http-body" 914 | version = "0.4.6" 915 | source = "registry+https://github.com/rust-lang/crates.io-index" 916 | checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" 917 | dependencies = [ 918 | "bytes", 919 | "http 0.2.12", 920 | "pin-project-lite", 921 | ] 922 | 923 | [[package]] 924 | name = "http-body" 925 | version = "1.0.1" 926 | source = "registry+https://github.com/rust-lang/crates.io-index" 927 | checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 928 | dependencies = [ 929 | "bytes", 930 | "http 1.3.1", 931 | ] 932 | 933 | [[package]] 934 | name = "http-body-util" 935 | version = "0.1.3" 936 | source = "registry+https://github.com/rust-lang/crates.io-index" 937 | checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" 938 | dependencies = [ 939 | "bytes", 940 | "futures-core", 941 | "http 1.3.1", 942 | "http-body 1.0.1", 943 | "pin-project-lite", 944 | ] 945 | 946 | [[package]] 947 | name = "httparse" 948 | version = "1.10.1" 949 | source = "registry+https://github.com/rust-lang/crates.io-index" 950 | checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 951 | 952 | [[package]] 953 | name = "httpdate" 954 | version = "1.0.3" 955 | source = "registry+https://github.com/rust-lang/crates.io-index" 956 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 957 | 958 | [[package]] 959 | name = "httpmock" 960 | version = "0.7.0" 961 | source = "registry+https://github.com/rust-lang/crates.io-index" 962 | checksum = "08ec9586ee0910472dec1a1f0f8acf52f0fdde93aea74d70d4a3107b4be0fd5b" 963 | dependencies = [ 964 | "assert-json-diff", 965 | "async-object-pool", 966 | "async-std", 967 | "async-trait", 968 | "base64 0.21.7", 969 | "basic-cookies", 970 | "crossbeam-utils", 971 | "form_urlencoded", 972 | "futures-util", 973 | "hyper 0.14.32", 974 | "lazy_static", 975 | "levenshtein", 976 | "log", 977 | "regex", 978 | "serde", 979 | "serde_json", 980 | "serde_regex", 981 | "similar", 982 | "tokio", 983 | "url", 984 | ] 985 | 986 | [[package]] 987 | name = "hyper" 988 | version = "0.14.32" 989 | source = "registry+https://github.com/rust-lang/crates.io-index" 990 | checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" 991 | dependencies = [ 992 | "bytes", 993 | "futures-channel", 994 | "futures-core", 995 | "futures-util", 996 | "http 0.2.12", 997 | "http-body 0.4.6", 998 | "httparse", 999 | "httpdate", 1000 | "itoa", 1001 | "pin-project-lite", 1002 | "socket2", 1003 | "tokio", 1004 | "tower-service", 1005 | "tracing", 1006 | "want", 1007 | ] 1008 | 1009 | [[package]] 1010 | name = "hyper" 1011 | version = "1.6.0" 1012 | source = "registry+https://github.com/rust-lang/crates.io-index" 1013 | checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" 1014 | dependencies = [ 1015 | "bytes", 1016 | "futures-channel", 1017 | "futures-util", 1018 | "h2", 1019 | "http 1.3.1", 1020 | "http-body 1.0.1", 1021 | "httparse", 1022 | "itoa", 1023 | "pin-project-lite", 1024 | "smallvec", 1025 | "tokio", 1026 | "want", 1027 | ] 1028 | 1029 | [[package]] 1030 | name = "hyper-rustls" 1031 | version = "0.27.5" 1032 | source = "registry+https://github.com/rust-lang/crates.io-index" 1033 | checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" 1034 | dependencies = [ 1035 | "futures-util", 1036 | "http 1.3.1", 1037 | "hyper 1.6.0", 1038 | "hyper-util", 1039 | "rustls", 1040 | "rustls-pki-types", 1041 | "tokio", 1042 | "tokio-rustls", 1043 | "tower-service", 1044 | ] 1045 | 1046 | [[package]] 1047 | name = "hyper-tls" 1048 | version = "0.6.0" 1049 | source = "registry+https://github.com/rust-lang/crates.io-index" 1050 | checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" 1051 | dependencies = [ 1052 | "bytes", 1053 | "http-body-util", 1054 | "hyper 1.6.0", 1055 | "hyper-util", 1056 | "native-tls", 1057 | "tokio", 1058 | "tokio-native-tls", 1059 | "tower-service", 1060 | ] 1061 | 1062 | [[package]] 1063 | name = "hyper-util" 1064 | version = "0.1.11" 1065 | source = "registry+https://github.com/rust-lang/crates.io-index" 1066 | checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" 1067 | dependencies = [ 1068 | "bytes", 1069 | "futures-channel", 1070 | "futures-util", 1071 | "http 1.3.1", 1072 | "http-body 1.0.1", 1073 | "hyper 1.6.0", 1074 | "libc", 1075 | "pin-project-lite", 1076 | "socket2", 1077 | "tokio", 1078 | "tower-service", 1079 | "tracing", 1080 | ] 1081 | 1082 | [[package]] 1083 | name = "icu_collections" 1084 | version = "1.5.0" 1085 | source = "registry+https://github.com/rust-lang/crates.io-index" 1086 | checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" 1087 | dependencies = [ 1088 | "displaydoc", 1089 | "yoke", 1090 | "zerofrom", 1091 | "zerovec", 1092 | ] 1093 | 1094 | [[package]] 1095 | name = "icu_locid" 1096 | version = "1.5.0" 1097 | source = "registry+https://github.com/rust-lang/crates.io-index" 1098 | checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" 1099 | dependencies = [ 1100 | "displaydoc", 1101 | "litemap", 1102 | "tinystr", 1103 | "writeable", 1104 | "zerovec", 1105 | ] 1106 | 1107 | [[package]] 1108 | name = "icu_locid_transform" 1109 | version = "1.5.0" 1110 | source = "registry+https://github.com/rust-lang/crates.io-index" 1111 | checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" 1112 | dependencies = [ 1113 | "displaydoc", 1114 | "icu_locid", 1115 | "icu_locid_transform_data", 1116 | "icu_provider", 1117 | "tinystr", 1118 | "zerovec", 1119 | ] 1120 | 1121 | [[package]] 1122 | name = "icu_locid_transform_data" 1123 | version = "1.5.1" 1124 | source = "registry+https://github.com/rust-lang/crates.io-index" 1125 | checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" 1126 | 1127 | [[package]] 1128 | name = "icu_normalizer" 1129 | version = "1.5.0" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" 1132 | dependencies = [ 1133 | "displaydoc", 1134 | "icu_collections", 1135 | "icu_normalizer_data", 1136 | "icu_properties", 1137 | "icu_provider", 1138 | "smallvec", 1139 | "utf16_iter", 1140 | "utf8_iter", 1141 | "write16", 1142 | "zerovec", 1143 | ] 1144 | 1145 | [[package]] 1146 | name = "icu_normalizer_data" 1147 | version = "1.5.1" 1148 | source = "registry+https://github.com/rust-lang/crates.io-index" 1149 | checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" 1150 | 1151 | [[package]] 1152 | name = "icu_properties" 1153 | version = "1.5.1" 1154 | source = "registry+https://github.com/rust-lang/crates.io-index" 1155 | checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" 1156 | dependencies = [ 1157 | "displaydoc", 1158 | "icu_collections", 1159 | "icu_locid_transform", 1160 | "icu_properties_data", 1161 | "icu_provider", 1162 | "tinystr", 1163 | "zerovec", 1164 | ] 1165 | 1166 | [[package]] 1167 | name = "icu_properties_data" 1168 | version = "1.5.1" 1169 | source = "registry+https://github.com/rust-lang/crates.io-index" 1170 | checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" 1171 | 1172 | [[package]] 1173 | name = "icu_provider" 1174 | version = "1.5.0" 1175 | source = "registry+https://github.com/rust-lang/crates.io-index" 1176 | checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" 1177 | dependencies = [ 1178 | "displaydoc", 1179 | "icu_locid", 1180 | "icu_provider_macros", 1181 | "stable_deref_trait", 1182 | "tinystr", 1183 | "writeable", 1184 | "yoke", 1185 | "zerofrom", 1186 | "zerovec", 1187 | ] 1188 | 1189 | [[package]] 1190 | name = "icu_provider_macros" 1191 | version = "1.5.0" 1192 | source = "registry+https://github.com/rust-lang/crates.io-index" 1193 | checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" 1194 | dependencies = [ 1195 | "proc-macro2", 1196 | "quote", 1197 | "syn 2.0.100", 1198 | ] 1199 | 1200 | [[package]] 1201 | name = "idna" 1202 | version = "1.0.3" 1203 | source = "registry+https://github.com/rust-lang/crates.io-index" 1204 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 1205 | dependencies = [ 1206 | "idna_adapter", 1207 | "smallvec", 1208 | "utf8_iter", 1209 | ] 1210 | 1211 | [[package]] 1212 | name = "idna_adapter" 1213 | version = "1.2.0" 1214 | source = "registry+https://github.com/rust-lang/crates.io-index" 1215 | checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" 1216 | dependencies = [ 1217 | "icu_normalizer", 1218 | "icu_properties", 1219 | ] 1220 | 1221 | [[package]] 1222 | name = "indexmap" 1223 | version = "2.9.0" 1224 | source = "registry+https://github.com/rust-lang/crates.io-index" 1225 | checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" 1226 | dependencies = [ 1227 | "equivalent", 1228 | "hashbrown", 1229 | ] 1230 | 1231 | [[package]] 1232 | name = "ipnet" 1233 | version = "2.11.0" 1234 | source = "registry+https://github.com/rust-lang/crates.io-index" 1235 | checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" 1236 | 1237 | [[package]] 1238 | name = "is_terminal_polyfill" 1239 | version = "1.70.1" 1240 | source = "registry+https://github.com/rust-lang/crates.io-index" 1241 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 1242 | 1243 | [[package]] 1244 | name = "itertools" 1245 | version = "0.11.0" 1246 | source = "registry+https://github.com/rust-lang/crates.io-index" 1247 | checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" 1248 | dependencies = [ 1249 | "either", 1250 | ] 1251 | 1252 | [[package]] 1253 | name = "itoa" 1254 | version = "1.0.15" 1255 | source = "registry+https://github.com/rust-lang/crates.io-index" 1256 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 1257 | 1258 | [[package]] 1259 | name = "js-sys" 1260 | version = "0.3.77" 1261 | source = "registry+https://github.com/rust-lang/crates.io-index" 1262 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 1263 | dependencies = [ 1264 | "once_cell", 1265 | "wasm-bindgen", 1266 | ] 1267 | 1268 | [[package]] 1269 | name = "kv-log-macro" 1270 | version = "1.0.7" 1271 | source = "registry+https://github.com/rust-lang/crates.io-index" 1272 | checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" 1273 | dependencies = [ 1274 | "log", 1275 | ] 1276 | 1277 | [[package]] 1278 | name = "lalrpop" 1279 | version = "0.20.2" 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" 1281 | checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" 1282 | dependencies = [ 1283 | "ascii-canvas", 1284 | "bit-set", 1285 | "ena", 1286 | "itertools", 1287 | "lalrpop-util", 1288 | "petgraph", 1289 | "pico-args", 1290 | "regex", 1291 | "regex-syntax", 1292 | "string_cache", 1293 | "term", 1294 | "tiny-keccak", 1295 | "unicode-xid", 1296 | "walkdir", 1297 | ] 1298 | 1299 | [[package]] 1300 | name = "lalrpop-util" 1301 | version = "0.20.2" 1302 | source = "registry+https://github.com/rust-lang/crates.io-index" 1303 | checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" 1304 | dependencies = [ 1305 | "regex-automata", 1306 | ] 1307 | 1308 | [[package]] 1309 | name = "lazy_static" 1310 | version = "1.5.0" 1311 | source = "registry+https://github.com/rust-lang/crates.io-index" 1312 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 1313 | 1314 | [[package]] 1315 | name = "levenshtein" 1316 | version = "1.0.5" 1317 | source = "registry+https://github.com/rust-lang/crates.io-index" 1318 | checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" 1319 | 1320 | [[package]] 1321 | name = "libc" 1322 | version = "0.2.171" 1323 | source = "registry+https://github.com/rust-lang/crates.io-index" 1324 | checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" 1325 | 1326 | [[package]] 1327 | name = "libredox" 1328 | version = "0.1.3" 1329 | source = "registry+https://github.com/rust-lang/crates.io-index" 1330 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 1331 | dependencies = [ 1332 | "bitflags", 1333 | "libc", 1334 | ] 1335 | 1336 | [[package]] 1337 | name = "linux-raw-sys" 1338 | version = "0.4.15" 1339 | source = "registry+https://github.com/rust-lang/crates.io-index" 1340 | checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 1341 | 1342 | [[package]] 1343 | name = "linux-raw-sys" 1344 | version = "0.9.3" 1345 | source = "registry+https://github.com/rust-lang/crates.io-index" 1346 | checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" 1347 | 1348 | [[package]] 1349 | name = "litemap" 1350 | version = "0.7.5" 1351 | source = "registry+https://github.com/rust-lang/crates.io-index" 1352 | checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" 1353 | 1354 | [[package]] 1355 | name = "lock_api" 1356 | version = "0.4.12" 1357 | source = "registry+https://github.com/rust-lang/crates.io-index" 1358 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 1359 | dependencies = [ 1360 | "autocfg", 1361 | "scopeguard", 1362 | ] 1363 | 1364 | [[package]] 1365 | name = "log" 1366 | version = "0.4.27" 1367 | source = "registry+https://github.com/rust-lang/crates.io-index" 1368 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 1369 | dependencies = [ 1370 | "value-bag", 1371 | ] 1372 | 1373 | [[package]] 1374 | name = "logbook_1" 1375 | version = "0.1.0" 1376 | dependencies = [ 1377 | "anyhow", 1378 | ] 1379 | 1380 | [[package]] 1381 | name = "logbook_2" 1382 | version = "0.1.0" 1383 | dependencies = [ 1384 | "anyhow", 1385 | ] 1386 | 1387 | [[package]] 1388 | name = "logbook_3" 1389 | version = "0.1.0" 1390 | dependencies = [ 1391 | "anyhow", 1392 | ] 1393 | 1394 | [[package]] 1395 | name = "logbook_4" 1396 | version = "0.1.0" 1397 | dependencies = [ 1398 | "anyhow", 1399 | "tempfile", 1400 | ] 1401 | 1402 | [[package]] 1403 | name = "memchr" 1404 | version = "2.7.4" 1405 | source = "registry+https://github.com/rust-lang/crates.io-index" 1406 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 1407 | 1408 | [[package]] 1409 | name = "memo_1" 1410 | version = "0.1.0" 1411 | dependencies = [ 1412 | "anyhow", 1413 | "tempfile", 1414 | ] 1415 | 1416 | [[package]] 1417 | name = "memo_2" 1418 | version = "0.1.0" 1419 | dependencies = [ 1420 | "anyhow", 1421 | "tempfile", 1422 | ] 1423 | 1424 | [[package]] 1425 | name = "memo_3" 1426 | version = "0.1.0" 1427 | dependencies = [ 1428 | "anyhow", 1429 | "serde", 1430 | "serde_json", 1431 | "tempfile", 1432 | ] 1433 | 1434 | [[package]] 1435 | name = "memo_4" 1436 | version = "0.1.0" 1437 | dependencies = [ 1438 | "anyhow", 1439 | "clap", 1440 | "serde", 1441 | "serde_json", 1442 | "tempfile", 1443 | ] 1444 | 1445 | [[package]] 1446 | name = "memo_5" 1447 | version = "0.1.0" 1448 | dependencies = [ 1449 | "anyhow", 1450 | "clap", 1451 | "serde", 1452 | "serde_json", 1453 | "tempfile", 1454 | ] 1455 | 1456 | [[package]] 1457 | name = "mime" 1458 | version = "0.3.17" 1459 | source = "registry+https://github.com/rust-lang/crates.io-index" 1460 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 1461 | 1462 | [[package]] 1463 | name = "miniz_oxide" 1464 | version = "0.8.7" 1465 | source = "registry+https://github.com/rust-lang/crates.io-index" 1466 | checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430" 1467 | dependencies = [ 1468 | "adler2", 1469 | ] 1470 | 1471 | [[package]] 1472 | name = "mio" 1473 | version = "1.0.3" 1474 | source = "registry+https://github.com/rust-lang/crates.io-index" 1475 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" 1476 | dependencies = [ 1477 | "libc", 1478 | "wasi 0.11.0+wasi-snapshot-preview1", 1479 | "windows-sys 0.52.0", 1480 | ] 1481 | 1482 | [[package]] 1483 | name = "native-tls" 1484 | version = "0.2.14" 1485 | source = "registry+https://github.com/rust-lang/crates.io-index" 1486 | checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" 1487 | dependencies = [ 1488 | "libc", 1489 | "log", 1490 | "openssl", 1491 | "openssl-probe", 1492 | "openssl-sys", 1493 | "schannel", 1494 | "security-framework", 1495 | "security-framework-sys", 1496 | "tempfile", 1497 | ] 1498 | 1499 | [[package]] 1500 | name = "new_debug_unreachable" 1501 | version = "1.0.6" 1502 | source = "registry+https://github.com/rust-lang/crates.io-index" 1503 | checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" 1504 | 1505 | [[package]] 1506 | name = "normalize-line-endings" 1507 | version = "0.3.0" 1508 | source = "registry+https://github.com/rust-lang/crates.io-index" 1509 | checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" 1510 | 1511 | [[package]] 1512 | name = "num-traits" 1513 | version = "0.2.19" 1514 | source = "registry+https://github.com/rust-lang/crates.io-index" 1515 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1516 | dependencies = [ 1517 | "autocfg", 1518 | ] 1519 | 1520 | [[package]] 1521 | name = "object" 1522 | version = "0.36.7" 1523 | source = "registry+https://github.com/rust-lang/crates.io-index" 1524 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 1525 | dependencies = [ 1526 | "memchr", 1527 | ] 1528 | 1529 | [[package]] 1530 | name = "once_cell" 1531 | version = "1.21.3" 1532 | source = "registry+https://github.com/rust-lang/crates.io-index" 1533 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 1534 | 1535 | [[package]] 1536 | name = "openssl" 1537 | version = "0.10.72" 1538 | source = "registry+https://github.com/rust-lang/crates.io-index" 1539 | checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" 1540 | dependencies = [ 1541 | "bitflags", 1542 | "cfg-if", 1543 | "foreign-types", 1544 | "libc", 1545 | "once_cell", 1546 | "openssl-macros", 1547 | "openssl-sys", 1548 | ] 1549 | 1550 | [[package]] 1551 | name = "openssl-macros" 1552 | version = "0.1.1" 1553 | source = "registry+https://github.com/rust-lang/crates.io-index" 1554 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 1555 | dependencies = [ 1556 | "proc-macro2", 1557 | "quote", 1558 | "syn 2.0.100", 1559 | ] 1560 | 1561 | [[package]] 1562 | name = "openssl-probe" 1563 | version = "0.1.6" 1564 | source = "registry+https://github.com/rust-lang/crates.io-index" 1565 | checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" 1566 | 1567 | [[package]] 1568 | name = "openssl-sys" 1569 | version = "0.9.107" 1570 | source = "registry+https://github.com/rust-lang/crates.io-index" 1571 | checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07" 1572 | dependencies = [ 1573 | "cc", 1574 | "libc", 1575 | "pkg-config", 1576 | "vcpkg", 1577 | ] 1578 | 1579 | [[package]] 1580 | name = "parking" 1581 | version = "2.2.1" 1582 | source = "registry+https://github.com/rust-lang/crates.io-index" 1583 | checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" 1584 | 1585 | [[package]] 1586 | name = "parking_lot" 1587 | version = "0.12.3" 1588 | source = "registry+https://github.com/rust-lang/crates.io-index" 1589 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 1590 | dependencies = [ 1591 | "lock_api", 1592 | "parking_lot_core", 1593 | ] 1594 | 1595 | [[package]] 1596 | name = "parking_lot_core" 1597 | version = "0.9.10" 1598 | source = "registry+https://github.com/rust-lang/crates.io-index" 1599 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 1600 | dependencies = [ 1601 | "cfg-if", 1602 | "libc", 1603 | "redox_syscall", 1604 | "smallvec", 1605 | "windows-targets 0.52.6", 1606 | ] 1607 | 1608 | [[package]] 1609 | name = "percent-encoding" 1610 | version = "2.3.1" 1611 | source = "registry+https://github.com/rust-lang/crates.io-index" 1612 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1613 | 1614 | [[package]] 1615 | name = "petgraph" 1616 | version = "0.6.5" 1617 | source = "registry+https://github.com/rust-lang/crates.io-index" 1618 | checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" 1619 | dependencies = [ 1620 | "fixedbitset", 1621 | "indexmap", 1622 | ] 1623 | 1624 | [[package]] 1625 | name = "phf_shared" 1626 | version = "0.11.3" 1627 | source = "registry+https://github.com/rust-lang/crates.io-index" 1628 | checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" 1629 | dependencies = [ 1630 | "siphasher", 1631 | ] 1632 | 1633 | [[package]] 1634 | name = "pico-args" 1635 | version = "0.5.0" 1636 | source = "registry+https://github.com/rust-lang/crates.io-index" 1637 | checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" 1638 | 1639 | [[package]] 1640 | name = "pin-project-lite" 1641 | version = "0.2.16" 1642 | source = "registry+https://github.com/rust-lang/crates.io-index" 1643 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 1644 | 1645 | [[package]] 1646 | name = "pin-utils" 1647 | version = "0.1.0" 1648 | source = "registry+https://github.com/rust-lang/crates.io-index" 1649 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1650 | 1651 | [[package]] 1652 | name = "piper" 1653 | version = "0.2.4" 1654 | source = "registry+https://github.com/rust-lang/crates.io-index" 1655 | checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" 1656 | dependencies = [ 1657 | "atomic-waker", 1658 | "fastrand", 1659 | "futures-io", 1660 | ] 1661 | 1662 | [[package]] 1663 | name = "pkg-config" 1664 | version = "0.3.32" 1665 | source = "registry+https://github.com/rust-lang/crates.io-index" 1666 | checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 1667 | 1668 | [[package]] 1669 | name = "polling" 1670 | version = "3.7.4" 1671 | source = "registry+https://github.com/rust-lang/crates.io-index" 1672 | checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" 1673 | dependencies = [ 1674 | "cfg-if", 1675 | "concurrent-queue", 1676 | "hermit-abi", 1677 | "pin-project-lite", 1678 | "rustix 0.38.44", 1679 | "tracing", 1680 | "windows-sys 0.59.0", 1681 | ] 1682 | 1683 | [[package]] 1684 | name = "precomputed-hash" 1685 | version = "0.1.1" 1686 | source = "registry+https://github.com/rust-lang/crates.io-index" 1687 | checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" 1688 | 1689 | [[package]] 1690 | name = "predicates" 1691 | version = "3.1.3" 1692 | source = "registry+https://github.com/rust-lang/crates.io-index" 1693 | checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" 1694 | dependencies = [ 1695 | "anstyle", 1696 | "difflib", 1697 | "float-cmp", 1698 | "normalize-line-endings", 1699 | "predicates-core", 1700 | "regex", 1701 | ] 1702 | 1703 | [[package]] 1704 | name = "predicates-core" 1705 | version = "1.0.9" 1706 | source = "registry+https://github.com/rust-lang/crates.io-index" 1707 | checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" 1708 | 1709 | [[package]] 1710 | name = "predicates-tree" 1711 | version = "1.0.12" 1712 | source = "registry+https://github.com/rust-lang/crates.io-index" 1713 | checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" 1714 | dependencies = [ 1715 | "predicates-core", 1716 | "termtree", 1717 | ] 1718 | 1719 | [[package]] 1720 | name = "proc-macro2" 1721 | version = "1.0.94" 1722 | source = "registry+https://github.com/rust-lang/crates.io-index" 1723 | checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" 1724 | dependencies = [ 1725 | "unicode-ident", 1726 | ] 1727 | 1728 | [[package]] 1729 | name = "quote" 1730 | version = "1.0.40" 1731 | source = "registry+https://github.com/rust-lang/crates.io-index" 1732 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 1733 | dependencies = [ 1734 | "proc-macro2", 1735 | ] 1736 | 1737 | [[package]] 1738 | name = "r-efi" 1739 | version = "5.2.0" 1740 | source = "registry+https://github.com/rust-lang/crates.io-index" 1741 | checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" 1742 | 1743 | [[package]] 1744 | name = "redox_syscall" 1745 | version = "0.5.11" 1746 | source = "registry+https://github.com/rust-lang/crates.io-index" 1747 | checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" 1748 | dependencies = [ 1749 | "bitflags", 1750 | ] 1751 | 1752 | [[package]] 1753 | name = "redox_users" 1754 | version = "0.4.6" 1755 | source = "registry+https://github.com/rust-lang/crates.io-index" 1756 | checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" 1757 | dependencies = [ 1758 | "getrandom 0.2.15", 1759 | "libredox", 1760 | "thiserror", 1761 | ] 1762 | 1763 | [[package]] 1764 | name = "regex" 1765 | version = "1.11.1" 1766 | source = "registry+https://github.com/rust-lang/crates.io-index" 1767 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 1768 | dependencies = [ 1769 | "aho-corasick", 1770 | "memchr", 1771 | "regex-automata", 1772 | "regex-syntax", 1773 | ] 1774 | 1775 | [[package]] 1776 | name = "regex-automata" 1777 | version = "0.4.9" 1778 | source = "registry+https://github.com/rust-lang/crates.io-index" 1779 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 1780 | dependencies = [ 1781 | "aho-corasick", 1782 | "memchr", 1783 | "regex-syntax", 1784 | ] 1785 | 1786 | [[package]] 1787 | name = "regex-syntax" 1788 | version = "0.8.5" 1789 | source = "registry+https://github.com/rust-lang/crates.io-index" 1790 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 1791 | 1792 | [[package]] 1793 | name = "reqwest" 1794 | version = "0.12.15" 1795 | source = "registry+https://github.com/rust-lang/crates.io-index" 1796 | checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" 1797 | dependencies = [ 1798 | "base64 0.22.1", 1799 | "bytes", 1800 | "encoding_rs", 1801 | "futures-channel", 1802 | "futures-core", 1803 | "futures-util", 1804 | "h2", 1805 | "http 1.3.1", 1806 | "http-body 1.0.1", 1807 | "http-body-util", 1808 | "hyper 1.6.0", 1809 | "hyper-rustls", 1810 | "hyper-tls", 1811 | "hyper-util", 1812 | "ipnet", 1813 | "js-sys", 1814 | "log", 1815 | "mime", 1816 | "native-tls", 1817 | "once_cell", 1818 | "percent-encoding", 1819 | "pin-project-lite", 1820 | "rustls-pemfile", 1821 | "serde", 1822 | "serde_json", 1823 | "serde_urlencoded", 1824 | "sync_wrapper", 1825 | "system-configuration", 1826 | "tokio", 1827 | "tokio-native-tls", 1828 | "tower", 1829 | "tower-service", 1830 | "url", 1831 | "wasm-bindgen", 1832 | "wasm-bindgen-futures", 1833 | "web-sys", 1834 | "windows-registry", 1835 | ] 1836 | 1837 | [[package]] 1838 | name = "ring" 1839 | version = "0.17.14" 1840 | source = "registry+https://github.com/rust-lang/crates.io-index" 1841 | checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" 1842 | dependencies = [ 1843 | "cc", 1844 | "cfg-if", 1845 | "getrandom 0.2.15", 1846 | "libc", 1847 | "untrusted", 1848 | "windows-sys 0.52.0", 1849 | ] 1850 | 1851 | [[package]] 1852 | name = "rustc-demangle" 1853 | version = "0.1.24" 1854 | source = "registry+https://github.com/rust-lang/crates.io-index" 1855 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1856 | 1857 | [[package]] 1858 | name = "rustix" 1859 | version = "0.38.44" 1860 | source = "registry+https://github.com/rust-lang/crates.io-index" 1861 | checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 1862 | dependencies = [ 1863 | "bitflags", 1864 | "errno", 1865 | "libc", 1866 | "linux-raw-sys 0.4.15", 1867 | "windows-sys 0.59.0", 1868 | ] 1869 | 1870 | [[package]] 1871 | name = "rustix" 1872 | version = "1.0.5" 1873 | source = "registry+https://github.com/rust-lang/crates.io-index" 1874 | checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" 1875 | dependencies = [ 1876 | "bitflags", 1877 | "errno", 1878 | "libc", 1879 | "linux-raw-sys 0.9.3", 1880 | "windows-sys 0.59.0", 1881 | ] 1882 | 1883 | [[package]] 1884 | name = "rustls" 1885 | version = "0.23.25" 1886 | source = "registry+https://github.com/rust-lang/crates.io-index" 1887 | checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c" 1888 | dependencies = [ 1889 | "once_cell", 1890 | "rustls-pki-types", 1891 | "rustls-webpki", 1892 | "subtle", 1893 | "zeroize", 1894 | ] 1895 | 1896 | [[package]] 1897 | name = "rustls-pemfile" 1898 | version = "2.2.0" 1899 | source = "registry+https://github.com/rust-lang/crates.io-index" 1900 | checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" 1901 | dependencies = [ 1902 | "rustls-pki-types", 1903 | ] 1904 | 1905 | [[package]] 1906 | name = "rustls-pki-types" 1907 | version = "1.11.0" 1908 | source = "registry+https://github.com/rust-lang/crates.io-index" 1909 | checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" 1910 | 1911 | [[package]] 1912 | name = "rustls-webpki" 1913 | version = "0.103.1" 1914 | source = "registry+https://github.com/rust-lang/crates.io-index" 1915 | checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" 1916 | dependencies = [ 1917 | "ring", 1918 | "rustls-pki-types", 1919 | "untrusted", 1920 | ] 1921 | 1922 | [[package]] 1923 | name = "rustversion" 1924 | version = "1.0.20" 1925 | source = "registry+https://github.com/rust-lang/crates.io-index" 1926 | checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" 1927 | 1928 | [[package]] 1929 | name = "ryu" 1930 | version = "1.0.20" 1931 | source = "registry+https://github.com/rust-lang/crates.io-index" 1932 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 1933 | 1934 | [[package]] 1935 | name = "same-file" 1936 | version = "1.0.6" 1937 | source = "registry+https://github.com/rust-lang/crates.io-index" 1938 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 1939 | dependencies = [ 1940 | "winapi-util", 1941 | ] 1942 | 1943 | [[package]] 1944 | name = "schannel" 1945 | version = "0.1.27" 1946 | source = "registry+https://github.com/rust-lang/crates.io-index" 1947 | checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" 1948 | dependencies = [ 1949 | "windows-sys 0.59.0", 1950 | ] 1951 | 1952 | [[package]] 1953 | name = "scopeguard" 1954 | version = "1.2.0" 1955 | source = "registry+https://github.com/rust-lang/crates.io-index" 1956 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1957 | 1958 | [[package]] 1959 | name = "security-framework" 1960 | version = "2.11.1" 1961 | source = "registry+https://github.com/rust-lang/crates.io-index" 1962 | checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 1963 | dependencies = [ 1964 | "bitflags", 1965 | "core-foundation", 1966 | "core-foundation-sys", 1967 | "libc", 1968 | "security-framework-sys", 1969 | ] 1970 | 1971 | [[package]] 1972 | name = "security-framework-sys" 1973 | version = "2.14.0" 1974 | source = "registry+https://github.com/rust-lang/crates.io-index" 1975 | checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" 1976 | dependencies = [ 1977 | "core-foundation-sys", 1978 | "libc", 1979 | ] 1980 | 1981 | [[package]] 1982 | name = "serde" 1983 | version = "1.0.219" 1984 | source = "registry+https://github.com/rust-lang/crates.io-index" 1985 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 1986 | dependencies = [ 1987 | "serde_derive", 1988 | ] 1989 | 1990 | [[package]] 1991 | name = "serde_derive" 1992 | version = "1.0.219" 1993 | source = "registry+https://github.com/rust-lang/crates.io-index" 1994 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 1995 | dependencies = [ 1996 | "proc-macro2", 1997 | "quote", 1998 | "syn 2.0.100", 1999 | ] 2000 | 2001 | [[package]] 2002 | name = "serde_json" 2003 | version = "1.0.140" 2004 | source = "registry+https://github.com/rust-lang/crates.io-index" 2005 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 2006 | dependencies = [ 2007 | "itoa", 2008 | "memchr", 2009 | "ryu", 2010 | "serde", 2011 | ] 2012 | 2013 | [[package]] 2014 | name = "serde_regex" 2015 | version = "1.1.0" 2016 | source = "registry+https://github.com/rust-lang/crates.io-index" 2017 | checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" 2018 | dependencies = [ 2019 | "regex", 2020 | "serde", 2021 | ] 2022 | 2023 | [[package]] 2024 | name = "serde_urlencoded" 2025 | version = "0.7.1" 2026 | source = "registry+https://github.com/rust-lang/crates.io-index" 2027 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 2028 | dependencies = [ 2029 | "form_urlencoded", 2030 | "itoa", 2031 | "ryu", 2032 | "serde", 2033 | ] 2034 | 2035 | [[package]] 2036 | name = "shlex" 2037 | version = "1.3.0" 2038 | source = "registry+https://github.com/rust-lang/crates.io-index" 2039 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 2040 | 2041 | [[package]] 2042 | name = "signal-hook-registry" 2043 | version = "1.4.2" 2044 | source = "registry+https://github.com/rust-lang/crates.io-index" 2045 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 2046 | dependencies = [ 2047 | "libc", 2048 | ] 2049 | 2050 | [[package]] 2051 | name = "similar" 2052 | version = "2.7.0" 2053 | source = "registry+https://github.com/rust-lang/crates.io-index" 2054 | checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" 2055 | 2056 | [[package]] 2057 | name = "siphasher" 2058 | version = "1.0.1" 2059 | source = "registry+https://github.com/rust-lang/crates.io-index" 2060 | checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" 2061 | 2062 | [[package]] 2063 | name = "slab" 2064 | version = "0.4.9" 2065 | source = "registry+https://github.com/rust-lang/crates.io-index" 2066 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 2067 | dependencies = [ 2068 | "autocfg", 2069 | ] 2070 | 2071 | [[package]] 2072 | name = "slim_1" 2073 | version = "0.1.0" 2074 | dependencies = [ 2075 | "anyhow", 2076 | "clap", 2077 | "walkdir", 2078 | ] 2079 | 2080 | [[package]] 2081 | name = "slim_2" 2082 | version = "0.1.0" 2083 | dependencies = [ 2084 | "anyhow", 2085 | "clap", 2086 | "walkdir", 2087 | ] 2088 | 2089 | [[package]] 2090 | name = "slim_3" 2091 | version = "0.1.0" 2092 | dependencies = [ 2093 | "anyhow", 2094 | "clap", 2095 | "walkdir", 2096 | ] 2097 | 2098 | [[package]] 2099 | name = "smallvec" 2100 | version = "1.15.0" 2101 | source = "registry+https://github.com/rust-lang/crates.io-index" 2102 | checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" 2103 | 2104 | [[package]] 2105 | name = "socket2" 2106 | version = "0.5.9" 2107 | source = "registry+https://github.com/rust-lang/crates.io-index" 2108 | checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" 2109 | dependencies = [ 2110 | "libc", 2111 | "windows-sys 0.52.0", 2112 | ] 2113 | 2114 | [[package]] 2115 | name = "stable_deref_trait" 2116 | version = "1.2.0" 2117 | source = "registry+https://github.com/rust-lang/crates.io-index" 2118 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 2119 | 2120 | [[package]] 2121 | name = "string_cache" 2122 | version = "0.8.9" 2123 | source = "registry+https://github.com/rust-lang/crates.io-index" 2124 | checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" 2125 | dependencies = [ 2126 | "new_debug_unreachable", 2127 | "parking_lot", 2128 | "phf_shared", 2129 | "precomputed-hash", 2130 | ] 2131 | 2132 | [[package]] 2133 | name = "strsim" 2134 | version = "0.11.1" 2135 | source = "registry+https://github.com/rust-lang/crates.io-index" 2136 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 2137 | 2138 | [[package]] 2139 | name = "subtle" 2140 | version = "2.6.1" 2141 | source = "registry+https://github.com/rust-lang/crates.io-index" 2142 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 2143 | 2144 | [[package]] 2145 | name = "syn" 2146 | version = "1.0.109" 2147 | source = "registry+https://github.com/rust-lang/crates.io-index" 2148 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 2149 | dependencies = [ 2150 | "proc-macro2", 2151 | "quote", 2152 | "unicode-ident", 2153 | ] 2154 | 2155 | [[package]] 2156 | name = "syn" 2157 | version = "2.0.100" 2158 | source = "registry+https://github.com/rust-lang/crates.io-index" 2159 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" 2160 | dependencies = [ 2161 | "proc-macro2", 2162 | "quote", 2163 | "unicode-ident", 2164 | ] 2165 | 2166 | [[package]] 2167 | name = "sync_wrapper" 2168 | version = "1.0.2" 2169 | source = "registry+https://github.com/rust-lang/crates.io-index" 2170 | checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 2171 | dependencies = [ 2172 | "futures-core", 2173 | ] 2174 | 2175 | [[package]] 2176 | name = "synstructure" 2177 | version = "0.13.1" 2178 | source = "registry+https://github.com/rust-lang/crates.io-index" 2179 | checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" 2180 | dependencies = [ 2181 | "proc-macro2", 2182 | "quote", 2183 | "syn 2.0.100", 2184 | ] 2185 | 2186 | [[package]] 2187 | name = "system-configuration" 2188 | version = "0.6.1" 2189 | source = "registry+https://github.com/rust-lang/crates.io-index" 2190 | checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" 2191 | dependencies = [ 2192 | "bitflags", 2193 | "core-foundation", 2194 | "system-configuration-sys", 2195 | ] 2196 | 2197 | [[package]] 2198 | name = "system-configuration-sys" 2199 | version = "0.6.0" 2200 | source = "registry+https://github.com/rust-lang/crates.io-index" 2201 | checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" 2202 | dependencies = [ 2203 | "core-foundation-sys", 2204 | "libc", 2205 | ] 2206 | 2207 | [[package]] 2208 | name = "tempfile" 2209 | version = "3.19.1" 2210 | source = "registry+https://github.com/rust-lang/crates.io-index" 2211 | checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" 2212 | dependencies = [ 2213 | "fastrand", 2214 | "getrandom 0.3.2", 2215 | "once_cell", 2216 | "rustix 1.0.5", 2217 | "windows-sys 0.59.0", 2218 | ] 2219 | 2220 | [[package]] 2221 | name = "term" 2222 | version = "0.7.0" 2223 | source = "registry+https://github.com/rust-lang/crates.io-index" 2224 | checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" 2225 | dependencies = [ 2226 | "dirs-next", 2227 | "rustversion", 2228 | "winapi", 2229 | ] 2230 | 2231 | [[package]] 2232 | name = "termtree" 2233 | version = "0.5.1" 2234 | source = "registry+https://github.com/rust-lang/crates.io-index" 2235 | checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" 2236 | 2237 | [[package]] 2238 | name = "thiserror" 2239 | version = "1.0.69" 2240 | source = "registry+https://github.com/rust-lang/crates.io-index" 2241 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 2242 | dependencies = [ 2243 | "thiserror-impl", 2244 | ] 2245 | 2246 | [[package]] 2247 | name = "thiserror-impl" 2248 | version = "1.0.69" 2249 | source = "registry+https://github.com/rust-lang/crates.io-index" 2250 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 2251 | dependencies = [ 2252 | "proc-macro2", 2253 | "quote", 2254 | "syn 2.0.100", 2255 | ] 2256 | 2257 | [[package]] 2258 | name = "timer_1" 2259 | version = "0.1.0" 2260 | dependencies = [ 2261 | "anyhow", 2262 | "clap", 2263 | ] 2264 | 2265 | [[package]] 2266 | name = "timer_2" 2267 | version = "0.1.0" 2268 | dependencies = [ 2269 | "anyhow", 2270 | "clap", 2271 | ] 2272 | 2273 | [[package]] 2274 | name = "tiny-keccak" 2275 | version = "2.0.2" 2276 | source = "registry+https://github.com/rust-lang/crates.io-index" 2277 | checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" 2278 | dependencies = [ 2279 | "crunchy", 2280 | ] 2281 | 2282 | [[package]] 2283 | name = "tinystr" 2284 | version = "0.7.6" 2285 | source = "registry+https://github.com/rust-lang/crates.io-index" 2286 | checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" 2287 | dependencies = [ 2288 | "displaydoc", 2289 | "zerovec", 2290 | ] 2291 | 2292 | [[package]] 2293 | name = "tokio" 2294 | version = "1.44.2" 2295 | source = "registry+https://github.com/rust-lang/crates.io-index" 2296 | checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" 2297 | dependencies = [ 2298 | "backtrace", 2299 | "bytes", 2300 | "libc", 2301 | "mio", 2302 | "pin-project-lite", 2303 | "signal-hook-registry", 2304 | "socket2", 2305 | "tokio-macros", 2306 | "windows-sys 0.52.0", 2307 | ] 2308 | 2309 | [[package]] 2310 | name = "tokio-macros" 2311 | version = "2.5.0" 2312 | source = "registry+https://github.com/rust-lang/crates.io-index" 2313 | checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" 2314 | dependencies = [ 2315 | "proc-macro2", 2316 | "quote", 2317 | "syn 2.0.100", 2318 | ] 2319 | 2320 | [[package]] 2321 | name = "tokio-native-tls" 2322 | version = "0.3.1" 2323 | source = "registry+https://github.com/rust-lang/crates.io-index" 2324 | checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 2325 | dependencies = [ 2326 | "native-tls", 2327 | "tokio", 2328 | ] 2329 | 2330 | [[package]] 2331 | name = "tokio-rustls" 2332 | version = "0.26.2" 2333 | source = "registry+https://github.com/rust-lang/crates.io-index" 2334 | checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" 2335 | dependencies = [ 2336 | "rustls", 2337 | "tokio", 2338 | ] 2339 | 2340 | [[package]] 2341 | name = "tokio-util" 2342 | version = "0.7.14" 2343 | source = "registry+https://github.com/rust-lang/crates.io-index" 2344 | checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" 2345 | dependencies = [ 2346 | "bytes", 2347 | "futures-core", 2348 | "futures-sink", 2349 | "pin-project-lite", 2350 | "tokio", 2351 | ] 2352 | 2353 | [[package]] 2354 | name = "tower" 2355 | version = "0.5.2" 2356 | source = "registry+https://github.com/rust-lang/crates.io-index" 2357 | checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" 2358 | dependencies = [ 2359 | "futures-core", 2360 | "futures-util", 2361 | "pin-project-lite", 2362 | "sync_wrapper", 2363 | "tokio", 2364 | "tower-layer", 2365 | "tower-service", 2366 | ] 2367 | 2368 | [[package]] 2369 | name = "tower-layer" 2370 | version = "0.3.3" 2371 | source = "registry+https://github.com/rust-lang/crates.io-index" 2372 | checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 2373 | 2374 | [[package]] 2375 | name = "tower-service" 2376 | version = "0.3.3" 2377 | source = "registry+https://github.com/rust-lang/crates.io-index" 2378 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 2379 | 2380 | [[package]] 2381 | name = "tracing" 2382 | version = "0.1.41" 2383 | source = "registry+https://github.com/rust-lang/crates.io-index" 2384 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 2385 | dependencies = [ 2386 | "pin-project-lite", 2387 | "tracing-core", 2388 | ] 2389 | 2390 | [[package]] 2391 | name = "tracing-core" 2392 | version = "0.1.33" 2393 | source = "registry+https://github.com/rust-lang/crates.io-index" 2394 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 2395 | dependencies = [ 2396 | "once_cell", 2397 | ] 2398 | 2399 | [[package]] 2400 | name = "try-lock" 2401 | version = "0.2.5" 2402 | source = "registry+https://github.com/rust-lang/crates.io-index" 2403 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 2404 | 2405 | [[package]] 2406 | name = "unicode-ident" 2407 | version = "1.0.18" 2408 | source = "registry+https://github.com/rust-lang/crates.io-index" 2409 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 2410 | 2411 | [[package]] 2412 | name = "unicode-xid" 2413 | version = "0.2.6" 2414 | source = "registry+https://github.com/rust-lang/crates.io-index" 2415 | checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" 2416 | 2417 | [[package]] 2418 | name = "untrusted" 2419 | version = "0.9.0" 2420 | source = "registry+https://github.com/rust-lang/crates.io-index" 2421 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 2422 | 2423 | [[package]] 2424 | name = "url" 2425 | version = "2.5.4" 2426 | source = "registry+https://github.com/rust-lang/crates.io-index" 2427 | checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 2428 | dependencies = [ 2429 | "form_urlencoded", 2430 | "idna", 2431 | "percent-encoding", 2432 | ] 2433 | 2434 | [[package]] 2435 | name = "utf16_iter" 2436 | version = "1.0.5" 2437 | source = "registry+https://github.com/rust-lang/crates.io-index" 2438 | checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" 2439 | 2440 | [[package]] 2441 | name = "utf8_iter" 2442 | version = "1.0.4" 2443 | source = "registry+https://github.com/rust-lang/crates.io-index" 2444 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 2445 | 2446 | [[package]] 2447 | name = "utf8parse" 2448 | version = "0.2.2" 2449 | source = "registry+https://github.com/rust-lang/crates.io-index" 2450 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 2451 | 2452 | [[package]] 2453 | name = "value-bag" 2454 | version = "1.11.1" 2455 | source = "registry+https://github.com/rust-lang/crates.io-index" 2456 | checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5" 2457 | 2458 | [[package]] 2459 | name = "vcpkg" 2460 | version = "0.2.15" 2461 | source = "registry+https://github.com/rust-lang/crates.io-index" 2462 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 2463 | 2464 | [[package]] 2465 | name = "wait-timeout" 2466 | version = "0.2.1" 2467 | source = "registry+https://github.com/rust-lang/crates.io-index" 2468 | checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" 2469 | dependencies = [ 2470 | "libc", 2471 | ] 2472 | 2473 | [[package]] 2474 | name = "walkdir" 2475 | version = "2.5.0" 2476 | source = "registry+https://github.com/rust-lang/crates.io-index" 2477 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 2478 | dependencies = [ 2479 | "same-file", 2480 | "winapi-util", 2481 | ] 2482 | 2483 | [[package]] 2484 | name = "want" 2485 | version = "0.3.1" 2486 | source = "registry+https://github.com/rust-lang/crates.io-index" 2487 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 2488 | dependencies = [ 2489 | "try-lock", 2490 | ] 2491 | 2492 | [[package]] 2493 | name = "wasi" 2494 | version = "0.11.0+wasi-snapshot-preview1" 2495 | source = "registry+https://github.com/rust-lang/crates.io-index" 2496 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2497 | 2498 | [[package]] 2499 | name = "wasi" 2500 | version = "0.14.2+wasi-0.2.4" 2501 | source = "registry+https://github.com/rust-lang/crates.io-index" 2502 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 2503 | dependencies = [ 2504 | "wit-bindgen-rt", 2505 | ] 2506 | 2507 | [[package]] 2508 | name = "wasm-bindgen" 2509 | version = "0.2.100" 2510 | source = "registry+https://github.com/rust-lang/crates.io-index" 2511 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 2512 | dependencies = [ 2513 | "cfg-if", 2514 | "once_cell", 2515 | "rustversion", 2516 | "wasm-bindgen-macro", 2517 | ] 2518 | 2519 | [[package]] 2520 | name = "wasm-bindgen-backend" 2521 | version = "0.2.100" 2522 | source = "registry+https://github.com/rust-lang/crates.io-index" 2523 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 2524 | dependencies = [ 2525 | "bumpalo", 2526 | "log", 2527 | "proc-macro2", 2528 | "quote", 2529 | "syn 2.0.100", 2530 | "wasm-bindgen-shared", 2531 | ] 2532 | 2533 | [[package]] 2534 | name = "wasm-bindgen-futures" 2535 | version = "0.4.50" 2536 | source = "registry+https://github.com/rust-lang/crates.io-index" 2537 | checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" 2538 | dependencies = [ 2539 | "cfg-if", 2540 | "js-sys", 2541 | "once_cell", 2542 | "wasm-bindgen", 2543 | "web-sys", 2544 | ] 2545 | 2546 | [[package]] 2547 | name = "wasm-bindgen-macro" 2548 | version = "0.2.100" 2549 | source = "registry+https://github.com/rust-lang/crates.io-index" 2550 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 2551 | dependencies = [ 2552 | "quote", 2553 | "wasm-bindgen-macro-support", 2554 | ] 2555 | 2556 | [[package]] 2557 | name = "wasm-bindgen-macro-support" 2558 | version = "0.2.100" 2559 | source = "registry+https://github.com/rust-lang/crates.io-index" 2560 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 2561 | dependencies = [ 2562 | "proc-macro2", 2563 | "quote", 2564 | "syn 2.0.100", 2565 | "wasm-bindgen-backend", 2566 | "wasm-bindgen-shared", 2567 | ] 2568 | 2569 | [[package]] 2570 | name = "wasm-bindgen-shared" 2571 | version = "0.2.100" 2572 | source = "registry+https://github.com/rust-lang/crates.io-index" 2573 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 2574 | dependencies = [ 2575 | "unicode-ident", 2576 | ] 2577 | 2578 | [[package]] 2579 | name = "weather_1" 2580 | version = "0.1.0" 2581 | dependencies = [ 2582 | "reqwest", 2583 | ] 2584 | 2585 | [[package]] 2586 | name = "weather_2" 2587 | version = "0.1.0" 2588 | dependencies = [ 2589 | "anyhow", 2590 | "clap", 2591 | "reqwest", 2592 | "serde_json", 2593 | "url", 2594 | ] 2595 | 2596 | [[package]] 2597 | name = "weather_3" 2598 | version = "0.1.0" 2599 | dependencies = [ 2600 | "anyhow", 2601 | "clap", 2602 | "httpmock", 2603 | "reqwest", 2604 | "serde", 2605 | "serde_json", 2606 | "url", 2607 | ] 2608 | 2609 | [[package]] 2610 | name = "weather_4" 2611 | version = "0.1.0" 2612 | dependencies = [ 2613 | "anyhow", 2614 | "clap", 2615 | "httpmock", 2616 | "reqwest", 2617 | "serde", 2618 | "serde_json", 2619 | "url", 2620 | ] 2621 | 2622 | [[package]] 2623 | name = "web-sys" 2624 | version = "0.3.77" 2625 | source = "registry+https://github.com/rust-lang/crates.io-index" 2626 | checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" 2627 | dependencies = [ 2628 | "js-sys", 2629 | "wasm-bindgen", 2630 | ] 2631 | 2632 | [[package]] 2633 | name = "winapi" 2634 | version = "0.3.9" 2635 | source = "registry+https://github.com/rust-lang/crates.io-index" 2636 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 2637 | dependencies = [ 2638 | "winapi-i686-pc-windows-gnu", 2639 | "winapi-x86_64-pc-windows-gnu", 2640 | ] 2641 | 2642 | [[package]] 2643 | name = "winapi-i686-pc-windows-gnu" 2644 | version = "0.4.0" 2645 | source = "registry+https://github.com/rust-lang/crates.io-index" 2646 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2647 | 2648 | [[package]] 2649 | name = "winapi-util" 2650 | version = "0.1.9" 2651 | source = "registry+https://github.com/rust-lang/crates.io-index" 2652 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 2653 | dependencies = [ 2654 | "windows-sys 0.59.0", 2655 | ] 2656 | 2657 | [[package]] 2658 | name = "winapi-x86_64-pc-windows-gnu" 2659 | version = "0.4.0" 2660 | source = "registry+https://github.com/rust-lang/crates.io-index" 2661 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2662 | 2663 | [[package]] 2664 | name = "windows-link" 2665 | version = "0.1.1" 2666 | source = "registry+https://github.com/rust-lang/crates.io-index" 2667 | checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" 2668 | 2669 | [[package]] 2670 | name = "windows-registry" 2671 | version = "0.4.0" 2672 | source = "registry+https://github.com/rust-lang/crates.io-index" 2673 | checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" 2674 | dependencies = [ 2675 | "windows-result", 2676 | "windows-strings", 2677 | "windows-targets 0.53.0", 2678 | ] 2679 | 2680 | [[package]] 2681 | name = "windows-result" 2682 | version = "0.3.2" 2683 | source = "registry+https://github.com/rust-lang/crates.io-index" 2684 | checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" 2685 | dependencies = [ 2686 | "windows-link", 2687 | ] 2688 | 2689 | [[package]] 2690 | name = "windows-strings" 2691 | version = "0.3.1" 2692 | source = "registry+https://github.com/rust-lang/crates.io-index" 2693 | checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" 2694 | dependencies = [ 2695 | "windows-link", 2696 | ] 2697 | 2698 | [[package]] 2699 | name = "windows-sys" 2700 | version = "0.52.0" 2701 | source = "registry+https://github.com/rust-lang/crates.io-index" 2702 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2703 | dependencies = [ 2704 | "windows-targets 0.52.6", 2705 | ] 2706 | 2707 | [[package]] 2708 | name = "windows-sys" 2709 | version = "0.59.0" 2710 | source = "registry+https://github.com/rust-lang/crates.io-index" 2711 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 2712 | dependencies = [ 2713 | "windows-targets 0.52.6", 2714 | ] 2715 | 2716 | [[package]] 2717 | name = "windows-targets" 2718 | version = "0.52.6" 2719 | source = "registry+https://github.com/rust-lang/crates.io-index" 2720 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2721 | dependencies = [ 2722 | "windows_aarch64_gnullvm 0.52.6", 2723 | "windows_aarch64_msvc 0.52.6", 2724 | "windows_i686_gnu 0.52.6", 2725 | "windows_i686_gnullvm 0.52.6", 2726 | "windows_i686_msvc 0.52.6", 2727 | "windows_x86_64_gnu 0.52.6", 2728 | "windows_x86_64_gnullvm 0.52.6", 2729 | "windows_x86_64_msvc 0.52.6", 2730 | ] 2731 | 2732 | [[package]] 2733 | name = "windows-targets" 2734 | version = "0.53.0" 2735 | source = "registry+https://github.com/rust-lang/crates.io-index" 2736 | checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" 2737 | dependencies = [ 2738 | "windows_aarch64_gnullvm 0.53.0", 2739 | "windows_aarch64_msvc 0.53.0", 2740 | "windows_i686_gnu 0.53.0", 2741 | "windows_i686_gnullvm 0.53.0", 2742 | "windows_i686_msvc 0.53.0", 2743 | "windows_x86_64_gnu 0.53.0", 2744 | "windows_x86_64_gnullvm 0.53.0", 2745 | "windows_x86_64_msvc 0.53.0", 2746 | ] 2747 | 2748 | [[package]] 2749 | name = "windows_aarch64_gnullvm" 2750 | version = "0.52.6" 2751 | source = "registry+https://github.com/rust-lang/crates.io-index" 2752 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2753 | 2754 | [[package]] 2755 | name = "windows_aarch64_gnullvm" 2756 | version = "0.53.0" 2757 | source = "registry+https://github.com/rust-lang/crates.io-index" 2758 | checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" 2759 | 2760 | [[package]] 2761 | name = "windows_aarch64_msvc" 2762 | version = "0.52.6" 2763 | source = "registry+https://github.com/rust-lang/crates.io-index" 2764 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2765 | 2766 | [[package]] 2767 | name = "windows_aarch64_msvc" 2768 | version = "0.53.0" 2769 | source = "registry+https://github.com/rust-lang/crates.io-index" 2770 | checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" 2771 | 2772 | [[package]] 2773 | name = "windows_i686_gnu" 2774 | version = "0.52.6" 2775 | source = "registry+https://github.com/rust-lang/crates.io-index" 2776 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2777 | 2778 | [[package]] 2779 | name = "windows_i686_gnu" 2780 | version = "0.53.0" 2781 | source = "registry+https://github.com/rust-lang/crates.io-index" 2782 | checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" 2783 | 2784 | [[package]] 2785 | name = "windows_i686_gnullvm" 2786 | version = "0.52.6" 2787 | source = "registry+https://github.com/rust-lang/crates.io-index" 2788 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2789 | 2790 | [[package]] 2791 | name = "windows_i686_gnullvm" 2792 | version = "0.53.0" 2793 | source = "registry+https://github.com/rust-lang/crates.io-index" 2794 | checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" 2795 | 2796 | [[package]] 2797 | name = "windows_i686_msvc" 2798 | version = "0.52.6" 2799 | source = "registry+https://github.com/rust-lang/crates.io-index" 2800 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2801 | 2802 | [[package]] 2803 | name = "windows_i686_msvc" 2804 | version = "0.53.0" 2805 | source = "registry+https://github.com/rust-lang/crates.io-index" 2806 | checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" 2807 | 2808 | [[package]] 2809 | name = "windows_x86_64_gnu" 2810 | version = "0.52.6" 2811 | source = "registry+https://github.com/rust-lang/crates.io-index" 2812 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2813 | 2814 | [[package]] 2815 | name = "windows_x86_64_gnu" 2816 | version = "0.53.0" 2817 | source = "registry+https://github.com/rust-lang/crates.io-index" 2818 | checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" 2819 | 2820 | [[package]] 2821 | name = "windows_x86_64_gnullvm" 2822 | version = "0.52.6" 2823 | source = "registry+https://github.com/rust-lang/crates.io-index" 2824 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2825 | 2826 | [[package]] 2827 | name = "windows_x86_64_gnullvm" 2828 | version = "0.53.0" 2829 | source = "registry+https://github.com/rust-lang/crates.io-index" 2830 | checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" 2831 | 2832 | [[package]] 2833 | name = "windows_x86_64_msvc" 2834 | version = "0.52.6" 2835 | source = "registry+https://github.com/rust-lang/crates.io-index" 2836 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2837 | 2838 | [[package]] 2839 | name = "windows_x86_64_msvc" 2840 | version = "0.53.0" 2841 | source = "registry+https://github.com/rust-lang/crates.io-index" 2842 | checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" 2843 | 2844 | [[package]] 2845 | name = "wit-bindgen-rt" 2846 | version = "0.39.0" 2847 | source = "registry+https://github.com/rust-lang/crates.io-index" 2848 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 2849 | dependencies = [ 2850 | "bitflags", 2851 | ] 2852 | 2853 | [[package]] 2854 | name = "write16" 2855 | version = "1.0.0" 2856 | source = "registry+https://github.com/rust-lang/crates.io-index" 2857 | checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" 2858 | 2859 | [[package]] 2860 | name = "writeable" 2861 | version = "0.5.5" 2862 | source = "registry+https://github.com/rust-lang/crates.io-index" 2863 | checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" 2864 | 2865 | [[package]] 2866 | name = "yoke" 2867 | version = "0.7.5" 2868 | source = "registry+https://github.com/rust-lang/crates.io-index" 2869 | checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" 2870 | dependencies = [ 2871 | "serde", 2872 | "stable_deref_trait", 2873 | "yoke-derive", 2874 | "zerofrom", 2875 | ] 2876 | 2877 | [[package]] 2878 | name = "yoke-derive" 2879 | version = "0.7.5" 2880 | source = "registry+https://github.com/rust-lang/crates.io-index" 2881 | checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" 2882 | dependencies = [ 2883 | "proc-macro2", 2884 | "quote", 2885 | "syn 2.0.100", 2886 | "synstructure", 2887 | ] 2888 | 2889 | [[package]] 2890 | name = "zerofrom" 2891 | version = "0.1.6" 2892 | source = "registry+https://github.com/rust-lang/crates.io-index" 2893 | checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 2894 | dependencies = [ 2895 | "zerofrom-derive", 2896 | ] 2897 | 2898 | [[package]] 2899 | name = "zerofrom-derive" 2900 | version = "0.1.6" 2901 | source = "registry+https://github.com/rust-lang/crates.io-index" 2902 | checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 2903 | dependencies = [ 2904 | "proc-macro2", 2905 | "quote", 2906 | "syn 2.0.100", 2907 | "synstructure", 2908 | ] 2909 | 2910 | [[package]] 2911 | name = "zeroize" 2912 | version = "1.8.1" 2913 | source = "registry+https://github.com/rust-lang/crates.io-index" 2914 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 2915 | 2916 | [[package]] 2917 | name = "zerovec" 2918 | version = "0.10.4" 2919 | source = "registry+https://github.com/rust-lang/crates.io-index" 2920 | checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" 2921 | dependencies = [ 2922 | "yoke", 2923 | "zerofrom", 2924 | "zerovec-derive", 2925 | ] 2926 | 2927 | [[package]] 2928 | name = "zerovec-derive" 2929 | version = "0.10.3" 2930 | source = "registry+https://github.com/rust-lang/crates.io-index" 2931 | checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" 2932 | dependencies = [ 2933 | "proc-macro2", 2934 | "quote", 2935 | "syn 2.0.100", 2936 | ] 2937 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "count_1", 4 | "count_2", 5 | "count_3", 6 | "hello_1", 7 | "hello_2", 8 | "hello_3", 9 | "count_4", 10 | "count_5", 11 | "count_6", 12 | "count_7", 13 | "count_8", 14 | "count_9", 15 | "logbook_1", 16 | "logbook_2", 17 | "logbook_3", 18 | "logbook_4", 19 | "memo_1", 20 | "memo_2", 21 | "memo_3", 22 | "memo_4", 23 | "memo_5", 24 | "weather_1", 25 | "weather_2", 26 | "weather_3", 27 | "weather_4", 28 | "timer_1", 29 | "slim_1", 30 | "timer_2", 31 | "slim_2", 32 | "slim_3", 33 | ] 34 | resolver = "2" 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 John Arundel 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 | # The Secrets of Rust: Tools 2 | 3 | [![](cover_small.png)](https://bitfieldconsulting.com/books/rust-tools) 4 | 5 | This repository contains exercises, solutions, and code samples from the book [The Secrets of Rust: Tools](https://bitfieldconsulting.com/books/rust-tools), by John Arundel. 6 | 7 | -------------------------------------------------------------------------------- /count_1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "count_1" 3 | version = "0.1.0" 4 | edition = "2024" 5 | authors = ["John Arundel "] 6 | license = "MIT OR Apache-2.0" 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /count_1/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::{BufRead, stdin}; 2 | 3 | fn main() { 4 | let input = stdin().lock(); 5 | let lines = input.lines().count(); 6 | println!("{lines} lines"); 7 | } 8 | -------------------------------------------------------------------------------- /count_2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "count_2" 3 | version = "0.1.0" 4 | edition = "2024" 5 | authors = ["John Arundel "] 6 | license = "MIT OR Apache-2.0" 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /count_2/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::io::BufRead; 2 | 3 | pub fn count_lines(input: impl BufRead) -> usize { 4 | input.lines().count() 5 | } 6 | 7 | #[cfg(test)] 8 | mod tests { 9 | use std::io::Cursor; 10 | 11 | use super::*; 12 | 13 | #[test] 14 | fn count_lines_fn_counts_lines_in_input() { 15 | let input = Cursor::new("line 1\nline 2\n"); 16 | let lines = count_lines(input); 17 | assert_eq!(lines, 2, "wrong line count"); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /count_2/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::stdin; 2 | 3 | use count_2::count_lines; 4 | 5 | fn main() { 6 | let lines = count_lines(stdin().lock()); 7 | println!("{lines} lines"); 8 | } 9 | -------------------------------------------------------------------------------- /count_3/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "count_3" 3 | version = "0.1.0" 4 | edition = "2024" 5 | authors = ["John Arundel "] 6 | license = "MIT OR Apache-2.0" 7 | -------------------------------------------------------------------------------- /count_3/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::io::{BufRead, Result}; 2 | 3 | /// Counts lines in `input`. 4 | /// 5 | /// # Errors 6 | /// 7 | /// Returns any error from [`BufRead::lines`]. 8 | pub fn count_lines(input: impl BufRead) -> Result { 9 | let mut count = 0; 10 | for line in input.lines() { 11 | line?; 12 | count += 1; 13 | } 14 | Ok(count) 15 | } 16 | 17 | #[cfg(test)] 18 | mod tests { 19 | use std::io::{BufReader, Cursor, Error, ErrorKind, Read}; 20 | 21 | use super::*; 22 | 23 | #[test] 24 | fn count_lines_fn_counts_lines_in_input() { 25 | let input = Cursor::new("line 1\nline 2\n"); 26 | let lines = count_lines(input).unwrap(); 27 | assert_eq!(lines, 2, "wrong line count"); 28 | } 29 | 30 | struct ErrorReader; 31 | 32 | impl Read for ErrorReader { 33 | fn read(&mut self, _buf: &mut [u8]) -> Result { 34 | Err(Error::new(ErrorKind::Other, "oh no")) 35 | } 36 | } 37 | 38 | #[test] 39 | fn count_lines_fn_returns_any_read_error() { 40 | let reader = BufReader::new(ErrorReader); 41 | let result = count_lines(reader); 42 | assert!(result.is_err(), "no error returned"); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /count_3/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{io::stdin, process}; 2 | 3 | use count_3::count_lines; 4 | 5 | fn main() { 6 | let res = count_lines(stdin().lock()); 7 | match res { 8 | Ok(lines) => println!("{lines} lines"), 9 | Err(e) => { 10 | eprintln!("{e}"); 11 | process::exit(1); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /count_4/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "count_4" 3 | version = "0.1.0" 4 | edition = "2024" 5 | authors = ["John Arundel "] 6 | license = "MIT OR Apache-2.0" 7 | 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /count_4/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::io::{BufRead, Result}; 2 | 3 | /// Counts lines in `input`. 4 | /// 5 | /// # Errors 6 | /// 7 | /// Returns any error from [`BufRead::lines`]. 8 | pub fn count_lines(input: impl BufRead) -> Result { 9 | let mut count = 0; 10 | for line in input.lines() { 11 | line?; 12 | count += 1; 13 | } 14 | Ok(count) 15 | } 16 | 17 | #[cfg(test)] 18 | mod tests { 19 | use std::io::{BufReader, Cursor, Error, ErrorKind, Read}; 20 | 21 | use super::*; 22 | 23 | #[test] 24 | fn count_lines_fn_counts_lines_in_input() { 25 | let input = Cursor::new("line 1\nline 2\n"); 26 | let lines = count_lines(input).unwrap(); 27 | assert_eq!(lines, 2, "wrong line count"); 28 | } 29 | 30 | struct ErrorReader; 31 | 32 | impl Read for ErrorReader { 33 | fn read(&mut self, _buf: &mut [u8]) -> Result { 34 | Err(Error::new(ErrorKind::Other, "oh no")) 35 | } 36 | } 37 | 38 | #[test] 39 | fn count_lines_fn_returns_any_read_error() { 40 | let reader = BufReader::new(ErrorReader); 41 | let result = count_lines(reader); 42 | assert!(result.is_err()); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /count_4/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{env, fs::File, io::BufReader, process}; 2 | 3 | use count_4::count_lines; 4 | 5 | fn main() { 6 | let Some(path) = env::args().nth(1) else { 7 | eprintln!("Usage: count "); 8 | process::exit(1); 9 | }; 10 | let file = File::open(&path) 11 | .map_err(|e| { 12 | eprintln!("{e}"); 13 | process::exit(1); 14 | }) 15 | .unwrap(); 16 | let file = BufReader::new(file); 17 | let lines = count_lines(file) 18 | .map_err(|e| { 19 | eprintln!("{e}"); 20 | process::exit(1); 21 | }) 22 | .unwrap(); 23 | println!("{lines} lines"); 24 | } 25 | -------------------------------------------------------------------------------- /count_5/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "count_5" 3 | version = "0.1.0" 4 | edition = "2024" 5 | authors = ["John Arundel "] 6 | license = "MIT OR Apache-2.0" 7 | 8 | 9 | [dependencies] 10 | anyhow = "1.0.86" 11 | -------------------------------------------------------------------------------- /count_5/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | use std::io::BufRead; 4 | 5 | /// Counts lines in `input`. 6 | /// 7 | /// # Errors 8 | /// 9 | /// Returns any error from [`BufRead::lines`]. 10 | pub fn count_lines(input: impl BufRead) -> Result { 11 | let mut count = 0; 12 | for line in input.lines() { 13 | line?; 14 | count += 1; 15 | } 16 | Ok(count) 17 | } 18 | 19 | #[cfg(test)] 20 | mod tests { 21 | use std::io::{ 22 | self, BufReader, Cursor, Error, ErrorKind, Read, 23 | }; 24 | 25 | use super::*; 26 | 27 | #[test] 28 | fn count_lines_fn_counts_lines_in_input() { 29 | let input = Cursor::new("line 1\nline 2\n"); 30 | let lines = count_lines(input).unwrap(); 31 | assert_eq!(lines, 2, "wrong line count"); 32 | } 33 | 34 | struct ErrorReader; 35 | 36 | impl Read for ErrorReader { 37 | fn read(&mut self, _buf: &mut [u8]) -> io::Result { 38 | Err(Error::new(ErrorKind::Other, "oh no")) 39 | } 40 | } 41 | 42 | #[test] 43 | fn count_lines_fn_returns_any_read_error() { 44 | let reader = BufReader::new(ErrorReader); 45 | let result = count_lines(reader); 46 | assert!(result.is_err()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /count_5/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | 3 | use std::{env, fs::File, io::BufReader}; 4 | 5 | use count_5::count_lines; 6 | 7 | fn main() -> Result<()> { 8 | let path = env::args().nth(1).context("Usage: count ")?; 9 | let file = File::open(&path)?; 10 | let file = BufReader::new(file); 11 | let lines = count_lines(file)?; 12 | println!("{lines} lines"); 13 | Ok(()) 14 | } 15 | -------------------------------------------------------------------------------- /count_6/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "count_6" 3 | version = "0.1.0" 4 | edition = "2024" 5 | authors = ["John Arundel "] 6 | license = "MIT OR Apache-2.0" 7 | 8 | 9 | [dependencies] 10 | anyhow = "1.0.86" 11 | -------------------------------------------------------------------------------- /count_6/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::io::{BufRead, Result}; 2 | 3 | /// Counts lines in `input`. 4 | /// 5 | /// # Errors 6 | /// 7 | /// Returns any error from [`BufRead::lines`]. 8 | pub fn count_lines(input: impl BufRead) -> Result { 9 | let mut count = 0; 10 | for line in input.lines() { 11 | line?; 12 | count += 1; 13 | } 14 | Ok(count) 15 | } 16 | 17 | #[cfg(test)] 18 | mod tests { 19 | use std::io::{ 20 | self, BufReader, Cursor, Error, ErrorKind, Read, 21 | }; 22 | 23 | use super::*; 24 | 25 | #[test] 26 | fn count_lines_fn_counts_lines_in_input() { 27 | let input = Cursor::new("line 1\nline 2\n"); 28 | let lines = count_lines(input).unwrap(); 29 | assert_eq!(lines, 2, "wrong line count"); 30 | } 31 | 32 | struct ErrorReader; 33 | 34 | impl Read for ErrorReader { 35 | fn read(&mut self, _buf: &mut [u8]) -> io::Result { 36 | Err(Error::new(ErrorKind::Other, "oh no")) 37 | } 38 | } 39 | 40 | #[test] 41 | fn count_lines_fn_returns_any_read_error() { 42 | let reader = BufReader::new(ErrorReader); 43 | let result = count_lines(reader); 44 | assert!(result.is_err()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /count_6/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | 3 | use std::{env, fs::File, io::BufReader}; 4 | 5 | use count_6::count_lines; 6 | 7 | fn main() -> Result<()> { 8 | for path in env::args().skip(1) { 9 | let file = 10 | File::open(&path).with_context(|| path.clone())?; 11 | let file = BufReader::new(file); 12 | let lines = 13 | count_lines(file).with_context(|| path.clone())?; 14 | println!("{path}: {lines} lines"); 15 | } 16 | Ok(()) 17 | } 18 | -------------------------------------------------------------------------------- /count_7/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "count_7" 3 | version = "0.1.0" 4 | edition = "2024" 5 | authors = ["John Arundel "] 6 | license = "MIT OR Apache-2.0" 7 | 8 | 9 | [dependencies] 10 | anyhow = "1.0.86" 11 | -------------------------------------------------------------------------------- /count_7/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | 3 | use std::{ 4 | fs::File, 5 | io::{BufRead, BufReader}, 6 | }; 7 | 8 | /// Counts lines in `input`. 9 | /// 10 | /// # Errors 11 | /// 12 | /// Returns any error from [`BufRead::lines`]. 13 | pub fn count_lines(input: impl BufRead) -> Result { 14 | let mut count = 0; 15 | for line in input.lines() { 16 | line?; 17 | count += 1; 18 | } 19 | Ok(count) 20 | } 21 | 22 | /// Counts lines in the file at `path`. 23 | /// 24 | /// # Errors 25 | /// 26 | /// Returns any error from [`File::open`] or [`count_lines`]. 27 | pub fn count_lines_in_path(path: &String) -> Result { 28 | let file = File::open(path).with_context(|| path.clone())?; 29 | let file = BufReader::new(file); 30 | count_lines(file).with_context(|| path.clone()) 31 | } 32 | 33 | #[cfg(test)] 34 | mod tests { 35 | use std::io::{self, Cursor, Error, ErrorKind, Read}; 36 | 37 | use super::*; 38 | 39 | #[test] 40 | fn count_lines_fn_counts_lines_in_input() { 41 | let input = Cursor::new("line 1\nline 2\n"); 42 | let lines = count_lines(input).unwrap(); 43 | assert_eq!(lines, 2, "wrong line count"); 44 | } 45 | 46 | struct ErrorReader; 47 | 48 | impl Read for ErrorReader { 49 | fn read(&mut self, _buf: &mut [u8]) -> io::Result { 50 | Err(Error::new(ErrorKind::Other, "oh no")) 51 | } 52 | } 53 | 54 | #[test] 55 | fn count_lines_fn_returns_any_read_error() { 56 | let reader = BufReader::new(ErrorReader); 57 | let result = count_lines(reader); 58 | assert!(result.is_err()); 59 | } 60 | 61 | #[test] 62 | fn count_lines_in_path_fn_counts_lines_in_given_file() { 63 | let path = String::from("tests/data/test.txt"); 64 | let lines = count_lines_in_path(&path).unwrap(); 65 | assert_eq!(lines, 2, "wrong line count"); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /count_7/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Result, bail}; 2 | 3 | use std::env; 4 | 5 | use count_7::count_lines_in_path; 6 | 7 | fn main() -> Result<()> { 8 | let args: Vec<_> = env::args().skip(1).collect(); 9 | if args.is_empty() { 10 | bail!("Usage: count ..."); 11 | } 12 | for path in args { 13 | println!("{path}: {} lines", count_lines_in_path(&path)?); 14 | } 15 | Ok(()) 16 | } 17 | -------------------------------------------------------------------------------- /count_7/tests/data/test.txt: -------------------------------------------------------------------------------- 1 | line 1 2 | line 2 -------------------------------------------------------------------------------- /count_8/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "count_8" 3 | version = "0.1.0" 4 | edition = "2024" 5 | authors = ["John Arundel "] 6 | license = "MIT OR Apache-2.0" 7 | 8 | [dependencies] 9 | anyhow = "1.0.86" 10 | 11 | [dev-dependencies] 12 | assert_cmd = "2.0.16" 13 | predicates = "3.1.2" 14 | -------------------------------------------------------------------------------- /count_8/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | 3 | use std::{ 4 | fs::File, 5 | io::{BufRead, BufReader}, 6 | }; 7 | 8 | #[derive(Default)] 9 | pub struct Count { 10 | pub lines: usize, 11 | pub words: usize, 12 | } 13 | 14 | /// Counts words and lines in the given reader. 15 | /// 16 | /// # Errors 17 | /// 18 | /// Returns any error from [`BufReader::read_line`]. 19 | pub fn count(mut input: impl BufRead) -> Result { 20 | let mut count = Count::default(); 21 | let mut line = String::new(); 22 | while input.read_line(&mut line)? > 0 { 23 | count.lines += 1; 24 | count.words += line.split_whitespace().count(); 25 | line.clear(); 26 | } 27 | Ok(count) 28 | } 29 | 30 | /// Counts words and lines in the file at `path`. 31 | /// 32 | /// # Errors 33 | /// 34 | /// Returns any error from [`File::open`] or [`count`]. 35 | pub fn count_in_path(path: &String) -> Result { 36 | let file = File::open(path).with_context(|| path.clone())?; 37 | let file = BufReader::new(file); 38 | count(file).with_context(|| path.clone()) 39 | } 40 | 41 | #[cfg(test)] 42 | mod tests { 43 | use std::io::{ 44 | self, BufReader, Cursor, Error, ErrorKind, Read, 45 | }; 46 | 47 | use super::*; 48 | 49 | #[test] 50 | fn count_counts_lines_and_words_in_input() { 51 | let input = Cursor::new("word1 word2\nword3"); 52 | let count = count(input).unwrap(); 53 | assert_eq!(count.lines, 2, "wrong line count"); 54 | assert_eq!(count.words, 3, "wrong word count"); 55 | } 56 | 57 | struct ErrorReader; 58 | 59 | impl Read for ErrorReader { 60 | fn read(&mut self, _buf: &mut [u8]) -> io::Result { 61 | Err(Error::new(ErrorKind::Other, "oh no")) 62 | } 63 | } 64 | 65 | #[test] 66 | fn count_returns_any_read_error() { 67 | let reader = BufReader::new(ErrorReader); 68 | let result = count(reader); 69 | assert!(result.is_err(), "no error returned"); 70 | } 71 | 72 | #[test] 73 | fn count_in_path_fn_counts_lines_and_words_in_given_file() { 74 | let path = String::from("tests/data/test.txt"); 75 | let count = count_in_path(&path).unwrap(); 76 | assert_eq!(count.lines, 2, "wrong line count"); 77 | assert_eq!(count.words, 4, "wrong word count"); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /count_8/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Result, bail}; 2 | 3 | use std::env; 4 | 5 | use count_8::count_in_path; 6 | 7 | fn main() -> Result<()> { 8 | let mut word_mode = false; 9 | let args: Vec<_> = env::args().skip(1).collect(); 10 | if args.is_empty() { 11 | bail!("Usage: count [-w] ..."); 12 | } 13 | for path in args { 14 | if path == "-w" { 15 | word_mode = true; 16 | continue; 17 | } 18 | let count = count_in_path(&path)?; 19 | if word_mode { 20 | println!("{path}: {} words", count.words); 21 | } else { 22 | println!("{path}: {} lines", count.lines); 23 | } 24 | } 25 | Ok(()) 26 | } 27 | -------------------------------------------------------------------------------- /count_8/tests/data/t2.txt: -------------------------------------------------------------------------------- 1 | line 1 2 | line 2 3 | line 3 -------------------------------------------------------------------------------- /count_8/tests/data/test.txt: -------------------------------------------------------------------------------- 1 | line 1 2 | line 2 -------------------------------------------------------------------------------- /count_8/tests/integration.rs: -------------------------------------------------------------------------------- 1 | use assert_cmd::Command; 2 | use predicates::prelude::*; 3 | 4 | #[test] 5 | fn binary_with_no_args_prints_usage() { 6 | Command::cargo_bin("count_8") 7 | .unwrap() 8 | .assert() 9 | .failure() 10 | .stderr(predicate::str::contains("Usage")); 11 | } 12 | 13 | #[test] 14 | fn binary_counts_lines_in_named_files() { 15 | Command::cargo_bin("count_8") 16 | .unwrap() 17 | .args(["tests/data/test.txt", "tests/data/t2.txt"]) 18 | .assert() 19 | .success() 20 | .stdout("tests/data/test.txt: 2 lines\ntests/data/t2.txt: 3 lines\n"); 21 | } 22 | 23 | #[test] 24 | fn binary_with_w_flag_counts_words() { 25 | Command::cargo_bin("count_8") 26 | .unwrap() 27 | .args(["-w", "tests/data/test.txt"]) 28 | .assert() 29 | .success() 30 | .stdout("tests/data/test.txt: 4 words\n"); 31 | } 32 | -------------------------------------------------------------------------------- /count_9/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "count_9" 3 | version = "0.1.0" 4 | edition = "2024" 5 | authors = ["John Arundel "] 6 | license = "MIT OR Apache-2.0" 7 | 8 | [dependencies] 9 | anyhow = "1.0.89" 10 | clap = { version = "4.5.17", features = ["derive"] } 11 | 12 | [dev-dependencies] 13 | assert_cmd = "2.0.16" 14 | predicates = "3.1.2" 15 | -------------------------------------------------------------------------------- /count_9/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | 3 | use std::{ 4 | fs::File, 5 | io::{BufRead, BufReader}, 6 | }; 7 | 8 | #[derive(Default)] 9 | pub struct Count { 10 | pub lines: usize, 11 | pub words: usize, 12 | } 13 | 14 | /// Counts words and lines in `input`. 15 | /// 16 | /// # Errors 17 | /// 18 | /// Returns any error from [`BufReader::read_line`]. 19 | pub fn count(mut input: impl BufRead) -> Result { 20 | let mut count = Count::default(); 21 | let mut line = String::new(); 22 | while input.read_line(&mut line)? > 0 { 23 | count.lines += 1; 24 | count.words += line.split_whitespace().count(); 25 | line.clear(); 26 | } 27 | Ok(count) 28 | } 29 | 30 | /// Counts words and lines in the file at `path`. 31 | /// 32 | /// # Errors 33 | /// 34 | /// Returns any error from [`File::open`] or [`count`]. 35 | pub fn count_in_path(path: &String) -> Result { 36 | let file = File::open(path).with_context(|| path.clone())?; 37 | let file = BufReader::new(file); 38 | count(file).with_context(|| path.clone()) 39 | } 40 | 41 | #[cfg(test)] 42 | mod tests { 43 | use std::io::{self, Cursor, Error, ErrorKind, Read}; 44 | 45 | use super::*; 46 | 47 | #[test] 48 | fn count_counts_lines_and_words_in_input() { 49 | let input = Cursor::new("word1 word2\nword3"); 50 | let count = count(input).unwrap(); 51 | assert_eq!(count.lines, 2, "wrong line count"); 52 | assert_eq!(count.words, 3, "wrong word count"); 53 | } 54 | 55 | struct ErrorReader; 56 | 57 | impl Read for ErrorReader { 58 | fn read(&mut self, _buf: &mut [u8]) -> io::Result { 59 | Err(Error::new(ErrorKind::Other, "oh no")) 60 | } 61 | } 62 | 63 | #[test] 64 | fn count_returns_any_read_error() { 65 | let reader = BufReader::new(ErrorReader); 66 | let result = count(reader); 67 | assert!(result.is_err()); 68 | } 69 | 70 | #[test] 71 | fn count_in_path_fn_counts_lines_and_words_in_given_file() { 72 | let path = String::from("tests/data/test.txt"); 73 | let count = count_in_path(&path).unwrap(); 74 | assert_eq!(count.lines, 2, "wrong line count"); 75 | assert_eq!(count.words, 4, "wrong word count"); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /count_9/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Parser; 3 | 4 | use count_9::count_in_path; 5 | 6 | #[derive(Parser)] 7 | /// Counts lines or words in the specified files 8 | struct Args { 9 | /// Counts words instead of lines 10 | #[arg(short, long)] 11 | words: bool, 12 | 13 | /// Files to be counted 14 | #[arg(required = true)] 15 | files: Vec, 16 | } 17 | 18 | fn main() -> Result<()> { 19 | let args = Args::parse(); 20 | for path in args.files { 21 | let count = count_in_path(&path)?; 22 | if args.words { 23 | println!("{path}: {} words", count.words); 24 | } else { 25 | println!("{path}: {} lines", count.lines); 26 | } 27 | } 28 | Ok(()) 29 | } 30 | -------------------------------------------------------------------------------- /count_9/tests/data/test.txt: -------------------------------------------------------------------------------- 1 | line 1 2 | line 2 -------------------------------------------------------------------------------- /count_9/tests/data/test2.txt: -------------------------------------------------------------------------------- 1 | line 1 2 | line 2 3 | line 3 -------------------------------------------------------------------------------- /count_9/tests/integration.rs: -------------------------------------------------------------------------------- 1 | use assert_cmd::Command; 2 | use predicates::prelude::*; 3 | 4 | #[test] 5 | fn binary_with_no_args_prints_usage() { 6 | Command::cargo_bin("count_9") 7 | .unwrap() 8 | .assert() 9 | .failure() 10 | .stderr(predicate::str::contains("Usage")); 11 | } 12 | 13 | #[test] 14 | fn binary_counts_lines_in_named_files() { 15 | let want = "tests/data/test.txt: 2 lines\ntests/data/test2.txt: 3 lines\n"; 16 | Command::cargo_bin("count_9") 17 | .unwrap() 18 | .args(["tests/data/test.txt", "tests/data/test2.txt"]) 19 | .assert() 20 | .success() 21 | .stdout(predicate::eq(want)); 22 | } 23 | 24 | #[test] 25 | fn binary_with_w_flag_counts_words() { 26 | let want = "tests/data/test.txt: 4 words\n"; 27 | Command::cargo_bin("count_9") 28 | .unwrap() 29 | .args(["-w", "tests/data/test.txt"]) 30 | .assert() 31 | .success() 32 | .stdout(predicate::eq(want)); 33 | } 34 | -------------------------------------------------------------------------------- /cover_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfield/tsr-tools/a17ba0f51a1df247cb90df742d0c2f9208e4ef8d/cover_small.png -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | # This template contains all of the possible sections and their default values 2 | 3 | # Note that all fields that take a lint level have these possible values: 4 | # * deny - An error will be produced and the check will fail 5 | # * warn - A warning will be produced, but the check will not fail 6 | # * allow - No warning or error will be produced, though in some cases a note 7 | # will be 8 | 9 | # The values provided in this template are the default values that will be used 10 | # when any section or field is not specified in your own configuration 11 | 12 | # Root options 13 | 14 | # The graph table configures how the dependency graph is constructed and thus 15 | # which crates the checks are performed against 16 | [graph] 17 | # If 1 or more target triples (and optionally, target_features) are specified, 18 | # only the specified targets will be checked when running `cargo deny check`. 19 | # This means, if a particular package is only ever used as a target specific 20 | # dependency, such as, for example, the `nix` crate only being used via the 21 | # `target_family = "unix"` configuration, that only having windows targets in 22 | # this list would mean the nix crate, as well as any of its exclusive 23 | # dependencies not shared by any other crates, would be ignored, as the target 24 | # list here is effectively saying which targets you are building for. 25 | targets = [ 26 | # The triple can be any string, but only the target triples built in to 27 | # rustc (as of 1.40) can be checked against actual config expressions 28 | #"x86_64-unknown-linux-musl", 29 | # You can also specify which target_features you promise are enabled for a 30 | # particular target. target_features are currently not validated against 31 | # the actual valid features supported by the target architecture. 32 | #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, 33 | ] 34 | # When creating the dependency graph used as the source of truth when checks are 35 | # executed, this field can be used to prune crates from the graph, removing them 36 | # from the view of cargo-deny. This is an extremely heavy hammer, as if a crate 37 | # is pruned from the graph, all of its dependencies will also be pruned unless 38 | # they are connected to another crate in the graph that hasn't been pruned, 39 | # so it should be used with care. The identifiers are [Package ID Specifications] 40 | # (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html) 41 | #exclude = [] 42 | # If true, metadata will be collected with `--all-features`. Note that this can't 43 | # be toggled off if true, if you want to conditionally enable `--all-features` it 44 | # is recommended to pass `--all-features` on the cmd line instead 45 | all-features = false 46 | # If true, metadata will be collected with `--no-default-features`. The same 47 | # caveat with `all-features` applies 48 | no-default-features = false 49 | # If set, these feature will be enabled when collecting metadata. If `--features` 50 | # is specified on the cmd line they will take precedence over this option. 51 | #features = [] 52 | 53 | # The output table provides options for how/if diagnostics are outputted 54 | [output] 55 | # When outputting inclusion graphs in diagnostics that include features, this 56 | # option can be used to specify the depth at which feature edges will be added. 57 | # This option is included since the graphs can be quite large and the addition 58 | # of features from the crate(s) to all of the graph roots can be far too verbose. 59 | # This option can be overridden via `--feature-depth` on the cmd line 60 | feature-depth = 1 61 | 62 | # This section is considered when running `cargo deny check advisories` 63 | # More documentation for the advisories section can be found here: 64 | # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html 65 | [advisories] 66 | # The path where the advisory databases are cloned/fetched into 67 | #db-path = "$CARGO_HOME/advisory-dbs" 68 | # The url(s) of the advisory databases to use 69 | #db-urls = ["https://github.com/rustsec/advisory-db"] 70 | # A list of advisory IDs to ignore. Note that ignored advisories will still 71 | # output a note when they are encountered. 72 | ignore = [ 73 | #"RUSTSEC-0000-0000", 74 | #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, 75 | #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish 76 | #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" }, 77 | ] 78 | # If this is true, then cargo deny will use the git executable to fetch advisory database. 79 | # If this is false, then it uses a built-in git library. 80 | # Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. 81 | # See Git Authentication for more information about setting up git authentication. 82 | #git-fetch-with-cli = true 83 | 84 | # This section is considered when running `cargo deny check licenses` 85 | # More documentation for the licenses section can be found here: 86 | # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html 87 | [licenses] 88 | # List of explicitly allowed licenses 89 | # See https://spdx.org/licenses/ for list of possible licenses 90 | # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. 91 | allow = [ 92 | "MIT", 93 | "Apache-2.0", 94 | "Unicode-DFS-2016", 95 | "Unicode-3.0", 96 | "CC0-1.0", 97 | "BSD-3-Clause" 98 | #"Apache-2.0 WITH LLVM-exception", 99 | ] 100 | # The confidence threshold for detecting a license from license text. 101 | # The higher the value, the more closely the license text must be to the 102 | # canonical license text of a valid SPDX license file. 103 | # [possible values: any between 0.0 and 1.0]. 104 | confidence-threshold = 0.8 105 | # Allow 1 or more licenses on a per-crate basis, so that particular licenses 106 | # aren't accepted for every possible crate as with the normal allow list 107 | exceptions = [ 108 | # Each entry is the crate and version constraint, and its specific allow 109 | # list 110 | #{ allow = ["Zlib"], crate = "adler32" }, 111 | ] 112 | 113 | # Some crates don't have (easily) machine readable licensing information, 114 | # adding a clarification entry for it allows you to manually specify the 115 | # licensing information 116 | #[[licenses.clarify]] 117 | # The package spec the clarification applies to 118 | #crate = "ring" 119 | # The SPDX expression for the license requirements of the crate 120 | #expression = "MIT AND ISC AND OpenSSL" 121 | # One or more files in the crate's source used as the "source of truth" for 122 | # the license expression. If the contents match, the clarification will be used 123 | # when running the license check, otherwise the clarification will be ignored 124 | # and the crate will be checked normally, which may produce warnings or errors 125 | # depending on the rest of your configuration 126 | #license-files = [ 127 | # Each entry is a crate relative path, and the (opaque) hash of its contents 128 | #{ path = "LICENSE", hash = 0xbd0eed23 } 129 | #] 130 | 131 | [licenses.private] 132 | # If true, ignores workspace crates that aren't published, or are only 133 | # published to private registries. 134 | # To see how to mark a crate as unpublished (to the official registry), 135 | # visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. 136 | ignore = true 137 | # One or more private registries that you might publish crates to, if a crate 138 | # is only published to private registries, and ignore is true, the crate will 139 | # not have its license(s) checked 140 | registries = [ 141 | #"https://sekretz.com/registry 142 | ] 143 | 144 | # This section is considered when running `cargo deny check bans`. 145 | # More documentation about the 'bans' section can be found here: 146 | # https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html 147 | [bans] 148 | # Lint level for when multiple versions of the same crate are detected 149 | multiple-versions = "warn" 150 | # Lint level for when a crate version requirement is `*` 151 | wildcards = "allow" 152 | # The graph highlighting used when creating dotgraphs for crates 153 | # with multiple versions 154 | # * lowest-version - The path to the lowest versioned duplicate is highlighted 155 | # * simplest-path - The path to the version with the fewest edges is highlighted 156 | # * all - Both lowest-version and simplest-path are used 157 | highlight = "all" 158 | # The default lint level for `default` features for crates that are members of 159 | # the workspace that is being checked. This can be overridden by allowing/denying 160 | # `default` on a crate-by-crate basis if desired. 161 | workspace-default-features = "allow" 162 | # The default lint level for `default` features for external crates that are not 163 | # members of the workspace. This can be overridden by allowing/denying `default` 164 | # on a crate-by-crate basis if desired. 165 | external-default-features = "allow" 166 | # List of crates that are allowed. Use with care! 167 | allow = [ 168 | #"ansi_term@0.11.0", 169 | #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" }, 170 | ] 171 | # List of crates to deny 172 | deny = [ 173 | #"ansi_term@0.11.0", 174 | #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" }, 175 | # Wrapper crates can optionally be specified to allow the crate when it 176 | # is a direct dependency of the otherwise banned crate 177 | #{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] }, 178 | ] 179 | 180 | # List of features to allow/deny 181 | # Each entry the name of a crate and a version range. If version is 182 | # not specified, all versions will be matched. 183 | #[[bans.features]] 184 | #crate = "reqwest" 185 | # Features to not allow 186 | #deny = ["json"] 187 | # Features to allow 188 | #allow = [ 189 | # "rustls", 190 | # "__rustls", 191 | # "__tls", 192 | # "hyper-rustls", 193 | # "rustls", 194 | # "rustls-pemfile", 195 | # "rustls-tls-webpki-roots", 196 | # "tokio-rustls", 197 | # "webpki-roots", 198 | #] 199 | # If true, the allowed features must exactly match the enabled feature set. If 200 | # this is set there is no point setting `deny` 201 | #exact = true 202 | 203 | # Certain crates/versions that will be skipped when doing duplicate detection. 204 | skip = [ 205 | #"ansi_term@0.11.0", 206 | #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" }, 207 | ] 208 | # Similarly to `skip` allows you to skip certain crates during duplicate 209 | # detection. Unlike skip, it also includes the entire tree of transitive 210 | # dependencies starting at the specified crate, up to a certain depth, which is 211 | # by default infinite. 212 | skip-tree = [ 213 | #"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies 214 | #{ crate = "ansi_term@0.11.0", depth = 20 }, 215 | ] 216 | 217 | # This section is considered when running `cargo deny check sources`. 218 | # More documentation about the 'sources' section can be found here: 219 | # https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html 220 | [sources] 221 | # Lint level for what to happen when a crate from a crate registry that is not 222 | # in the allow list is encountered 223 | unknown-registry = "warn" 224 | # Lint level for what to happen when a crate from a git repository that is not 225 | # in the allow list is encountered 226 | unknown-git = "warn" 227 | # List of URLs for allowed crate registries. Defaults to the crates.io index 228 | # if not specified. If it is specified but empty, no registries are allowed. 229 | allow-registry = ["https://github.com/rust-lang/crates.io-index"] 230 | # List of URLs for allowed Git repositories 231 | allow-git = [] 232 | 233 | [sources.allow-org] 234 | # 1 or more github.com organizations to allow git sources for 235 | #github = [""] 236 | # 1 or more gitlab.com organizations to allow git sources for 237 | #gitlab = [""] 238 | # 1 or more bitbucket.org organizations to allow git sources for 239 | #bitbucket = [""] 240 | -------------------------------------------------------------------------------- /hello_1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello_1" 3 | version = "0.1.0" 4 | edition = "2024" 5 | authors = ["John Arundel "] 6 | license = "MIT OR Apache-2.0" 7 | 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /hello_1/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /hello_2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello_2" 3 | version = "0.1.0" 4 | edition = "2024" 5 | authors = ["John Arundel "] 6 | license = "MIT OR Apache-2.0" 7 | 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /hello_2/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub fn print() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /hello_2/src/main.rs: -------------------------------------------------------------------------------- 1 | use hello_2::print; 2 | 3 | fn main() { 4 | print(); 5 | } 6 | -------------------------------------------------------------------------------- /hello_3/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello_3" 3 | version = "0.1.0" 4 | edition = "2024" 5 | authors = ["John Arundel "] 6 | license = "MIT OR Apache-2.0" 7 | -------------------------------------------------------------------------------- /hello_3/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[must_use] 2 | pub fn world() -> String { 3 | String::from("Hello, world!") 4 | } 5 | 6 | #[test] 7 | fn world_returns_hello_world() { 8 | assert_eq!(world(), "Hello, world!", "wrong message"); 9 | } 10 | -------------------------------------------------------------------------------- /hello_3/src/main.rs: -------------------------------------------------------------------------------- 1 | use hello_3 as hello; 2 | 3 | use hello::world; 4 | 5 | fn main() { 6 | println!("{}", world()); 7 | } 8 | -------------------------------------------------------------------------------- /logbook_1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "logbook_1" 3 | version = "0.1.0" 4 | edition = "2024" 5 | authors = ["John Arundel "] 6 | license = "MIT OR Apache-2.0" 7 | 8 | [dependencies] 9 | anyhow = "1.0.89" 10 | -------------------------------------------------------------------------------- /logbook_1/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | use std::{fs::File, io::Write}; 4 | 5 | fn main() -> Result<()> { 6 | let mut logbook = File::options() 7 | .create(true) 8 | .append(true) 9 | .open("logbook.txt")?; 10 | writeln!(logbook, "Hello, logbook!")?; 11 | Ok(()) 12 | } 13 | -------------------------------------------------------------------------------- /logbook_2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "logbook_2" 3 | version = "0.1.0" 4 | edition = "2024" 5 | authors = ["John Arundel "] 6 | license = "MIT OR Apache-2.0" 7 | 8 | [dependencies] 9 | anyhow = "1.0.89" 10 | -------------------------------------------------------------------------------- /logbook_2/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Result, bail}; 2 | 3 | use std::{env, fs::File, io::Write}; 4 | 5 | fn main() -> Result<()> { 6 | let args: Vec<_> = env::args().skip(1).collect(); 7 | if args.is_empty() { 8 | bail!("Usage: logbook "); 9 | } 10 | let mut logbook = File::options() 11 | .create(true) 12 | .append(true) 13 | .open("logbook.txt")?; 14 | writeln!(logbook, "{}", args.join(" "))?; 15 | Ok(()) 16 | } 17 | -------------------------------------------------------------------------------- /logbook_3/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "logbook_3" 3 | version = "0.1.0" 4 | edition = "2024" 5 | authors = ["John Arundel "] 6 | license = "MIT OR Apache-2.0" 7 | 8 | [dependencies] 9 | anyhow = "1.0.89" 10 | -------------------------------------------------------------------------------- /logbook_3/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | use std::{ 4 | env, 5 | fs::{self, File}, 6 | io::Write, 7 | }; 8 | 9 | fn main() -> Result<()> { 10 | let args: Vec<_> = env::args().skip(1).collect(); 11 | if args.is_empty() { 12 | if fs::exists("logbook.txt")? { 13 | let text = fs::read_to_string("logbook.txt")?; 14 | print!("{text}"); 15 | } else { 16 | println!("Logbook is empty"); 17 | } 18 | } else { 19 | let mut logbook = File::options() 20 | .create(true) 21 | .append(true) 22 | .open("logbook.txt")?; 23 | writeln!(logbook, "{}", args.join(" "))?; 24 | } 25 | Ok(()) 26 | } 27 | -------------------------------------------------------------------------------- /logbook_4/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "logbook_4" 3 | version = "0.1.0" 4 | edition = "2024" 5 | license = "MIT OR Apache-2.0" 6 | description = """ 7 | Record observations in a logbook file, or list previous observations. 8 | """ 9 | keywords = ["cli", "utility", "text"] 10 | categories = ["command-line-utilities"] 11 | repository = "https://github.com/bitfield/tsr-tools" 12 | authors = ["Russ Tation "] 13 | 14 | [dependencies] 15 | anyhow = "1.0.89" 16 | 17 | [dev-dependencies] 18 | tempfile = "3.13.0" 19 | -------------------------------------------------------------------------------- /logbook_4/README.md: -------------------------------------------------------------------------------- 1 | # logbook 2 | 3 | `logbook` is a Rust library (and CLI tool) for recording observations in a text file. It can append a string to the logbook file, or get the whole contents of the logbook file as a string. 4 | -------------------------------------------------------------------------------- /logbook_4/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Records observations in a logbook file, or lists previous 2 | //! observations. 3 | use anyhow::Result; 4 | 5 | use std::fs::{self, File}; 6 | use std::io::Write; 7 | use std::path::Path; 8 | 9 | /// Reads the contents of the logbook file at `path`. 10 | /// 11 | /// Returns [`None`] if the file does not exist or is empty. 12 | /// 13 | /// # Errors 14 | /// 15 | /// Returns any error from [`fs::exists`] or [`fs::read_to_string`]. 16 | pub fn read(path: impl AsRef) -> Result> { 17 | if fs::exists(&path)? { 18 | let text = fs::read_to_string(path)?; 19 | if text.is_empty() { 20 | Ok(None) 21 | } else { 22 | Ok(Some(text)) 23 | } 24 | } else { 25 | Ok(None) 26 | } 27 | } 28 | 29 | /// Appends `msg` to the logbook file at `path`, creating the file if necessary. 30 | /// 31 | /// # Errors 32 | /// 33 | /// Returns any error from [`open`](fs::OpenOptions::open) or [`writeln!`]. 34 | pub fn append(path: impl AsRef, msg: &str) -> Result<()> { 35 | let mut logbook = 36 | File::options().create(true).append(true).open(path)?; 37 | writeln!(logbook, "{msg}")?; 38 | Ok(()) 39 | } 40 | 41 | #[cfg(test)] 42 | mod tests { 43 | use tempfile::tempdir; 44 | 45 | use super::*; 46 | 47 | #[test] 48 | fn read_reads_contents_of_file_as_string() { 49 | let text = read("tests/data/logbook.txt").unwrap().unwrap(); 50 | assert_eq!(text.trim_end(), "hello world", "wrong text"); 51 | } 52 | 53 | #[test] 54 | fn read_returns_none_for_empty_file() { 55 | let text = read("tests/data/empty.txt").unwrap(); 56 | assert_eq!(text, None, "expected None"); 57 | } 58 | 59 | #[test] 60 | fn read_returns_none_if_file_does_not_exist() { 61 | let text = read("tests/data/bogus.txt").unwrap(); 62 | assert_eq!(text, None, "expected None"); 63 | } 64 | 65 | #[test] 66 | fn append_creates_file_if_necessary() { 67 | let dir = tempdir().unwrap(); 68 | let path = dir.path().join("newlog.txt"); 69 | append(&path, "hello logbook").unwrap(); 70 | let text = fs::read_to_string(path).unwrap(); 71 | assert_eq!(text, "hello logbook\n", "wrong text"); 72 | } 73 | 74 | #[test] 75 | fn append_appends_line_to_existing_file() { 76 | let dir = tempdir().unwrap(); 77 | let path = dir.path().join("logbook.txt"); 78 | fs::write(&path, "hello\n").unwrap(); 79 | append(&path, "logbook").unwrap(); 80 | let text = fs::read_to_string(path).unwrap(); 81 | assert_eq!(text, "hello\nlogbook\n", "wrong text"); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /logbook_4/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | use std::env; 4 | 5 | use logbook_4 as logbook; 6 | 7 | fn main() -> Result<()> { 8 | let args: Vec<_> = env::args().skip(1).collect(); 9 | if args.is_empty() { 10 | if let Some(text) = logbook::read("logbook.txt")? { 11 | print!("{text}"); 12 | } else { 13 | println!("Logbook is empty"); 14 | } 15 | } else { 16 | logbook::append("logbook.txt", &args.join(" "))?; 17 | } 18 | Ok(()) 19 | } 20 | -------------------------------------------------------------------------------- /logbook_4/tests/data/empty.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfield/tsr-tools/a17ba0f51a1df247cb90df742d0c2f9208e4ef8d/logbook_4/tests/data/empty.txt -------------------------------------------------------------------------------- /logbook_4/tests/data/logbook.txt: -------------------------------------------------------------------------------- 1 | hello world 2 | -------------------------------------------------------------------------------- /memo_1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "memo_1" 3 | version = "0.1.0" 4 | edition = "2024" 5 | license = "MIT OR Apache-2.0" 6 | 7 | [dependencies] 8 | anyhow = "1.0.93" 9 | 10 | [dev-dependencies] 11 | tempfile = "3.14.0" 12 | -------------------------------------------------------------------------------- /memo_1/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs::{self, File}, 3 | io::{BufRead, BufReader, Result}, 4 | path::Path, 5 | }; 6 | 7 | /// Reads the contents of the memo file at `path`. 8 | /// 9 | /// Returns an empty [`Vec`] if the file does not exist or is empty. 10 | /// 11 | /// # Errors 12 | /// 13 | /// Returns any error from [`fs::exists`], [`File::open`], or [`BufRead::lines`]. 14 | pub fn open(path: impl AsRef) -> Result> { 15 | if fs::exists(&path)? { 16 | let file = BufReader::new(File::open(&path)?); 17 | file.lines().collect() 18 | } else { 19 | Ok(Vec::new()) 20 | } 21 | } 22 | 23 | /// Writes `memos` to the file at `path`, creating it if necessary. 24 | /// 25 | /// # Errors 26 | /// 27 | /// Returns any error from [`fs::write`]. 28 | pub fn sync( 29 | memos: &[String], 30 | path: impl AsRef, 31 | ) -> Result<()> { 32 | fs::write(&path, memos.join("\n")) 33 | } 34 | 35 | #[cfg(test)] 36 | mod tests { 37 | use tempfile::tempdir; 38 | 39 | use super::*; 40 | 41 | #[test] 42 | fn open_returns_data_from_given_file() { 43 | let memos = open("tests/data/memos.txt").unwrap(); 44 | assert_eq!(memos, vec!["foo", "bar"], "wrong data"); 45 | } 46 | 47 | #[test] 48 | fn open_returns_empty_vec_for_missing_file() { 49 | let memos = open("bogus.txt").unwrap(); 50 | assert!(memos.is_empty(), "vec not empty"); 51 | } 52 | 53 | #[test] 54 | fn sync_writes_vec_to_file() { 55 | let dir = tempdir().unwrap(); 56 | let path = dir.path().join("memos.txt"); 57 | let vec = vec!["foo".to_string(), "bar".to_string()]; 58 | sync(&vec, &path).unwrap(); 59 | let memos = open(&path).unwrap(); 60 | assert_eq!(memos, vec, "wrong data"); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /memo_1/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | use std::env; 4 | 5 | use memo::{open, sync}; 6 | use memo_1 as memo; 7 | 8 | fn main() -> Result<()> { 9 | let mut memos = open("memos.txt")?; 10 | let args: Vec<_> = env::args().skip(1).collect(); 11 | if args.is_empty() { 12 | for memo in &memos { 13 | println!("{memo}"); 14 | } 15 | } else { 16 | let memo = args.join(" "); 17 | memos.push(memo); 18 | sync(&memos, "memos.txt")?; 19 | } 20 | Ok(()) 21 | } 22 | -------------------------------------------------------------------------------- /memo_1/tests/data/memos.txt: -------------------------------------------------------------------------------- 1 | foo 2 | bar 3 | -------------------------------------------------------------------------------- /memo_2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "memo_2" 3 | version = "0.1.0" 4 | edition = "2024" 5 | license = "MIT OR Apache-2.0" 6 | 7 | [dependencies] 8 | anyhow = "1.0.93" 9 | 10 | [dev-dependencies] 11 | tempfile = "3.14.0" 12 | -------------------------------------------------------------------------------- /memo_2/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs::{self, File}, 3 | io::{BufRead, BufReader, Result}, 4 | path::{Path, PathBuf}, 5 | }; 6 | 7 | pub struct Memos { 8 | path: PathBuf, 9 | pub inner: Vec, 10 | } 11 | 12 | impl Memos { 13 | /// Reads the contents of the memo file at `path`. 14 | /// 15 | /// Returns an empty [`Memos`] if the file does not exist or is empty. 16 | /// 17 | /// # Errors 18 | /// 19 | /// Returns any error from [`fs::exists`], [`File::open`], or [`BufRead::lines`]. 20 | pub fn open(path: impl AsRef) -> Result { 21 | let mut memos = Self { 22 | path: PathBuf::from(path.as_ref()), 23 | inner: Vec::new(), 24 | }; 25 | if fs::exists(&path)? { 26 | let file = BufReader::new(File::open(&path)?); 27 | for memo in file.lines() { 28 | memos.inner.push(memo?); 29 | } 30 | } 31 | Ok(memos) 32 | } 33 | 34 | /// Writes `memos` to the file at `path`, creating it if necessary. 35 | /// 36 | /// # Errors 37 | /// 38 | /// Returns any error from [`fs::write`]. 39 | pub fn sync(&self) -> Result<()> { 40 | fs::write(&self.path, self.inner.join("\n")) 41 | } 42 | } 43 | 44 | #[cfg(test)] 45 | mod tests { 46 | use super::*; 47 | 48 | #[test] 49 | fn open_returns_data_from_given_file() { 50 | let memos = Memos::open("tests/data/memos.txt").unwrap(); 51 | assert_eq!(memos.inner, vec!["foo", "bar"], "wrong data"); 52 | } 53 | 54 | #[test] 55 | fn open_returns_empty_vec_for_missing_file() { 56 | let memos = Memos::open("bogus.txt").unwrap(); 57 | assert!(memos.inner.is_empty(), "vec not empty"); 58 | } 59 | 60 | use tempfile::tempdir; 61 | 62 | #[test] 63 | fn sync_writes_vec_to_file() { 64 | let dir = tempdir().unwrap(); 65 | let path = dir.path().join("memos.txt"); 66 | let memos = Memos { 67 | path: path.clone(), 68 | inner: vec!["foo".to_string(), "bar".to_string()], 69 | }; 70 | memos.sync().unwrap(); 71 | let memos = Memos::open(&path).unwrap(); 72 | assert_eq!( 73 | memos.inner, 74 | vec!["foo".to_string(), "bar".to_string()], 75 | "wrong data" 76 | ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /memo_2/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | use std::env; 4 | 5 | use memo::Memos; 6 | use memo_2 as memo; 7 | 8 | fn main() -> Result<()> { 9 | let mut memos = Memos::open("memos.txt")?; 10 | let args: Vec<_> = env::args().skip(1).collect(); 11 | if args.is_empty() { 12 | for memo in &memos.inner { 13 | println!("{memo}"); 14 | } 15 | } else { 16 | let memo = args.join(" "); 17 | memos.inner.push(memo); 18 | memos.sync()?; 19 | } 20 | Ok(()) 21 | } 22 | -------------------------------------------------------------------------------- /memo_2/tests/data/memos.txt: -------------------------------------------------------------------------------- 1 | foo 2 | bar 3 | -------------------------------------------------------------------------------- /memo_3/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "memo_3" 3 | version = "0.1.0" 4 | edition = "2024" 5 | license = "MIT OR Apache-2.0" 6 | 7 | [dependencies] 8 | anyhow = "1.0.93" 9 | serde = { version = "1.0.215", features = ["derive"] } 10 | serde_json = "1.0.133" 11 | 12 | [dev-dependencies] 13 | tempfile = "3.14.0" 14 | -------------------------------------------------------------------------------- /memo_3/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | use std::{ 4 | fmt::Display, 5 | fs::{self, File}, 6 | io::{BufReader, BufWriter}, 7 | path::{Path, PathBuf}, 8 | }; 9 | 10 | use serde::{Deserialize, Serialize}; 11 | 12 | #[derive(Serialize, Deserialize, PartialEq)] 13 | pub struct Memos { 14 | path: PathBuf, 15 | pub inner: Vec, 16 | } 17 | 18 | impl Memos { 19 | /// Reads the contents of the memo file at `path`. 20 | /// 21 | /// Returns an empty [`Memos`] if the file does not exist or is empty. 22 | /// 23 | /// # Errors 24 | /// 25 | /// Returns any error from [`fs::exists`], [`File::open`], or 26 | /// [`serde_json::from_reader`]. 27 | pub fn open(path: impl AsRef) -> Result { 28 | let mut memos = Self { 29 | path: PathBuf::from(path.as_ref()), 30 | inner: Vec::new(), 31 | }; 32 | if fs::exists(&path)? { 33 | let file = File::open(path)?; 34 | memos.inner = 35 | serde_json::from_reader(BufReader::new(file))?; 36 | } 37 | Ok(memos) 38 | } 39 | 40 | /// Writes `memos` to the file at `path`, creating it if necessary. 41 | /// 42 | /// # Errors 43 | /// 44 | /// Returns any error from [`File::create`] or [`serde_json::to_writer`]. 45 | pub fn sync(&self) -> Result<()> { 46 | let file = File::create(&self.path)?; 47 | serde_json::to_writer(BufWriter::new(file), &self.inner)?; 48 | Ok(()) 49 | } 50 | } 51 | 52 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 53 | pub struct Memo { 54 | pub text: String, 55 | pub status: Status, 56 | } 57 | 58 | impl Display for Memo { 59 | fn fmt( 60 | &self, 61 | f: &mut std::fmt::Formatter<'_>, 62 | ) -> std::fmt::Result { 63 | write!(f, "{} {}", self.status, self.text) 64 | } 65 | } 66 | 67 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 68 | pub enum Status { 69 | Pending, 70 | Done, 71 | } 72 | 73 | impl Display for Status { 74 | fn fmt( 75 | &self, 76 | f: &mut std::fmt::Formatter<'_>, 77 | ) -> std::fmt::Result { 78 | write!( 79 | f, 80 | "{}", 81 | match self { 82 | Self::Pending => "-", 83 | Self::Done => "x", 84 | } 85 | ) 86 | } 87 | } 88 | 89 | #[cfg(test)] 90 | mod tests { 91 | use tempfile::tempdir; 92 | 93 | use super::*; 94 | 95 | #[test] 96 | fn open_returns_empty_vec_for_missing_file() { 97 | let memos = Memos::open("bogus.json").unwrap(); 98 | assert!(memos.inner.is_empty(), "vec not empty"); 99 | } 100 | 101 | #[test] 102 | fn round_trip_via_sync_and_open_preserves_data() { 103 | let dir = tempdir().unwrap(); 104 | let path = dir.path().join("memos.json"); 105 | let memos = Memos { 106 | path: path.clone(), 107 | inner: vec![ 108 | Memo { 109 | text: "foo".to_string(), 110 | status: Status::Pending, 111 | }, 112 | Memo { 113 | text: "bar".to_string(), 114 | status: Status::Pending, 115 | }, 116 | ], 117 | }; 118 | memos.sync().unwrap(); 119 | let memos_2 = Memos::open(&path).unwrap(); 120 | assert_eq!(memos.inner, memos_2.inner, "wrong data"); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /memo_3/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | use std::env; 4 | 5 | use memo::{Memo, Memos, Status}; 6 | use memo_3 as memo; 7 | 8 | fn main() -> Result<()> { 9 | let mut memos = Memos::open("memos.json")?; 10 | let args: Vec<_> = env::args().skip(1).collect(); 11 | if args.is_empty() { 12 | for memo in &memos.inner { 13 | println!("{memo}"); 14 | } 15 | } else { 16 | let text = args.join(" "); 17 | memos.inner.push(Memo { 18 | text, 19 | status: Status::Pending, 20 | }); 21 | memos.sync()?; 22 | } 23 | Ok(()) 24 | } 25 | -------------------------------------------------------------------------------- /memo_4/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "memo_4" 3 | version = "0.1.0" 4 | edition = "2024" 5 | license = "MIT OR Apache-2.0" 6 | 7 | [dependencies] 8 | anyhow = "1.0.93" 9 | clap = { version = "4.5.21", features = ["derive"] } 10 | serde = { version = "1.0.215", features = ["derive"] } 11 | serde_json = "1.0.133" 12 | 13 | [dev-dependencies] 14 | tempfile = "3.14.0" 15 | -------------------------------------------------------------------------------- /memo_4/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | use std::{ 4 | fmt::Display, 5 | fs::{self, File}, 6 | io::{BufReader, BufWriter}, 7 | path::{Path, PathBuf}, 8 | }; 9 | 10 | use serde::{Deserialize, Serialize}; 11 | 12 | #[derive(Serialize, Deserialize, PartialEq)] 13 | pub struct Memos { 14 | path: PathBuf, 15 | pub inner: Vec, 16 | } 17 | 18 | impl Memos { 19 | pub fn find_all(&mut self, text: &str) -> Vec<&mut Memo> { 20 | self.inner 21 | .iter_mut() 22 | .filter(|m| m.text.contains(text)) 23 | .collect() 24 | } 25 | 26 | pub fn purge_done(&mut self) { 27 | self.inner.retain(|m| m.status != Status::Done); 28 | } 29 | 30 | /// Reads the contents of the memo file at `path`. 31 | /// 32 | /// Returns an empty [`Memos`] if the file does not exist or is empty. 33 | /// 34 | /// # Errors 35 | /// 36 | /// Returns any error from [`fs::exists`], [`File::open`], or 37 | /// [`serde_json::from_reader`]. 38 | pub fn open(path: impl AsRef) -> Result { 39 | let mut memos = Self { 40 | path: PathBuf::from(path.as_ref()), 41 | inner: Vec::new(), 42 | }; 43 | if fs::exists(&path)? { 44 | let file = File::open(path)?; 45 | memos.inner = 46 | serde_json::from_reader(BufReader::new(file))?; 47 | } 48 | Ok(memos) 49 | } 50 | 51 | /// Writes `memos` to the file at `path`, creating it if necessary. 52 | /// 53 | /// # Errors 54 | /// 55 | /// Returns any error from [`File::create`] or [`serde_json::to_writer`]. 56 | pub fn sync(&self) -> Result<()> { 57 | let file = File::create(&self.path)?; 58 | serde_json::to_writer(BufWriter::new(file), &self.inner)?; 59 | Ok(()) 60 | } 61 | } 62 | 63 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 64 | pub struct Memo { 65 | pub text: String, 66 | pub status: Status, 67 | } 68 | 69 | impl Display for Memo { 70 | fn fmt( 71 | &self, 72 | f: &mut std::fmt::Formatter<'_>, 73 | ) -> std::fmt::Result { 74 | write!(f, "{} {}", self.status, self.text) 75 | } 76 | } 77 | 78 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 79 | pub enum Status { 80 | Pending, 81 | Done, 82 | } 83 | 84 | impl Display for Status { 85 | fn fmt( 86 | &self, 87 | f: &mut std::fmt::Formatter<'_>, 88 | ) -> std::fmt::Result { 89 | write!( 90 | f, 91 | "{}", 92 | match self { 93 | Self::Pending => "-", 94 | Self::Done => "x", 95 | } 96 | ) 97 | } 98 | } 99 | 100 | #[cfg(test)] 101 | mod tests { 102 | use tempfile::tempdir; 103 | 104 | use super::*; 105 | 106 | #[test] 107 | fn open_returns_empty_vec_for_missing_file() { 108 | let memos = Memos::open("bogus.json").unwrap(); 109 | assert!(memos.inner.is_empty(), "vec not empty"); 110 | } 111 | 112 | #[test] 113 | fn round_trip_via_sync_and_open_preserves_data() { 114 | let dir = tempdir().unwrap(); 115 | let path = dir.path().join("memos.json"); 116 | let memos = Memos { 117 | path: path.clone(), 118 | inner: vec![ 119 | Memo { 120 | text: "foo".to_string(), 121 | status: Status::Pending, 122 | }, 123 | Memo { 124 | text: "bar".to_string(), 125 | status: Status::Pending, 126 | }, 127 | ], 128 | }; 129 | memos.sync().unwrap(); 130 | let memos_2 = Memos::open(&path).unwrap(); 131 | assert_eq!(memos.inner, memos_2.inner, "wrong data"); 132 | } 133 | 134 | #[test] 135 | fn find_all_fn_returns_all_memos_matching_substring() { 136 | let mut memos = Memos { 137 | path: PathBuf::from("dummy"), 138 | inner: vec![ 139 | Memo { 140 | text: "foo".to_string(), 141 | status: Status::Pending, 142 | }, 143 | Memo { 144 | text: "bar".to_string(), 145 | status: Status::Pending, 146 | }, 147 | Memo { 148 | text: "food".to_string(), 149 | status: Status::Pending, 150 | }, 151 | ], 152 | }; 153 | let found: Vec<&mut Memo> = memos.find_all("foo"); 154 | assert_eq!(found.len(), 2, "wrong number of matches"); 155 | assert_eq!(found[0].text, "foo", "wrong match"); 156 | assert_eq!(found[1].text, "food", "wrong match"); 157 | } 158 | 159 | #[test] 160 | fn purge_done_fn_deletes_all_memos_with_done_status() { 161 | let mut memos = Memos { 162 | path: PathBuf::from("dummy"), 163 | inner: vec![ 164 | Memo { 165 | text: "foo".to_string(), 166 | status: Status::Done, 167 | }, 168 | Memo { 169 | text: "bar".to_string(), 170 | status: Status::Done, 171 | }, 172 | Memo { 173 | text: "food".to_string(), 174 | status: Status::Pending, 175 | }, 176 | ], 177 | }; 178 | memos.purge_done(); 179 | assert_eq!( 180 | memos.inner, 181 | vec![Memo { 182 | text: "food".to_string(), 183 | status: Status::Pending, 184 | },], 185 | "wrong data" 186 | ); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /memo_4/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Parser; 3 | 4 | use memo::{Memo, Memos, Status}; 5 | use memo_4 as memo; 6 | 7 | #[derive(Parser)] 8 | /// Stores and manages simple reminders. 9 | struct Args { 10 | /// Marks all matching memos as done 11 | #[arg(short, long)] 12 | done: bool, 13 | /// Text of the memo to store or mark as done 14 | text: Vec, 15 | } 16 | 17 | fn main() -> Result<()> { 18 | let args = Args::parse(); 19 | let mut memos = Memos::open("memos.json")?; 20 | let text = args.text.join(" "); 21 | if args.done { 22 | for m in memos.find_all(&text) { 23 | m.status = Status::Done; 24 | println!("Marked \"{}\" as done.", m.text); 25 | } 26 | memos.sync()?; 27 | } else if args.text.is_empty() { 28 | for memo in &memos.inner { 29 | println!("{memo}"); 30 | } 31 | } else { 32 | memos.inner.push(Memo { 33 | text: text.clone(), 34 | status: Status::Pending, 35 | }); 36 | println!("Added \"{}\" as a new memo.", &text); 37 | memos.sync()?; 38 | } 39 | Ok(()) 40 | } 41 | -------------------------------------------------------------------------------- /memo_5/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "memo_5" 3 | version = "0.1.0" 4 | edition = "2024" 5 | license = "MIT OR Apache-2.0" 6 | 7 | [dependencies] 8 | anyhow = "1.0.93" 9 | clap = { version = "4.5.21", features = ["derive"] } 10 | serde = { version = "1.0.215", features = ["derive"] } 11 | serde_json = "1.0.133" 12 | 13 | [dev-dependencies] 14 | tempfile = "3.14.0" 15 | -------------------------------------------------------------------------------- /memo_5/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | use std::{ 4 | fmt::Display, 5 | fs::{self, File}, 6 | io::{BufReader, BufWriter}, 7 | path::{Path, PathBuf}, 8 | }; 9 | 10 | use serde::{Deserialize, Serialize}; 11 | 12 | #[derive(Serialize, Deserialize, PartialEq)] 13 | pub struct Memos { 14 | path: PathBuf, 15 | pub inner: Vec, 16 | } 17 | 18 | impl Memos { 19 | pub fn find_all(&mut self, text: &str) -> Vec<&mut Memo> { 20 | self.inner 21 | .iter_mut() 22 | .filter(|m| m.text.contains(text)) 23 | .collect() 24 | } 25 | 26 | pub fn purge_done(&mut self) { 27 | self.inner.retain(|m| m.status != Status::Done); 28 | } 29 | 30 | /// Reads the contents of the memo file at `path`. 31 | /// 32 | /// Returns an empty [`Memos`] if the file does not exist or is empty. 33 | /// 34 | /// # Errors 35 | /// 36 | /// Returns any error from [`fs::exists`], [`File::open`], or 37 | /// [`serde_json::from_reader`]. 38 | pub fn open(path: impl AsRef) -> Result { 39 | let mut memos = Self { 40 | path: PathBuf::from(path.as_ref()), 41 | inner: Vec::new(), 42 | }; 43 | if fs::exists(&path)? { 44 | let file = File::open(path)?; 45 | memos.inner = 46 | serde_json::from_reader(BufReader::new(file))?; 47 | } 48 | Ok(memos) 49 | } 50 | 51 | /// Writes `memos` to the file at `path`, creating it if necessary. 52 | /// 53 | /// # Errors 54 | /// 55 | /// Returns any error from [`File::create`] or [`serde_json::to_writer`]. 56 | pub fn sync(&self) -> Result<()> { 57 | let file = File::create(&self.path)?; 58 | serde_json::to_writer(BufWriter::new(file), &self.inner)?; 59 | Ok(()) 60 | } 61 | } 62 | 63 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 64 | pub struct Memo { 65 | pub text: String, 66 | pub status: Status, 67 | } 68 | 69 | impl Display for Memo { 70 | fn fmt( 71 | &self, 72 | f: &mut std::fmt::Formatter<'_>, 73 | ) -> std::fmt::Result { 74 | write!(f, "{} {}", self.status, self.text) 75 | } 76 | } 77 | 78 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 79 | pub enum Status { 80 | Pending, 81 | Done, 82 | } 83 | 84 | impl Display for Status { 85 | fn fmt( 86 | &self, 87 | f: &mut std::fmt::Formatter<'_>, 88 | ) -> std::fmt::Result { 89 | write!( 90 | f, 91 | "{}", 92 | match self { 93 | Self::Pending => "-", 94 | Self::Done => "x", 95 | } 96 | ) 97 | } 98 | } 99 | 100 | #[cfg(test)] 101 | mod tests { 102 | use tempfile::tempdir; 103 | 104 | use super::*; 105 | 106 | #[test] 107 | fn open_returns_empty_vec_for_missing_file() { 108 | let memos = Memos::open("bogus.json").unwrap(); 109 | assert!(memos.inner.is_empty(), "vec not empty"); 110 | } 111 | 112 | #[test] 113 | fn round_trip_via_sync_and_open_preserves_data() { 114 | let dir = tempdir().unwrap(); 115 | let path = dir.path().join("memos.json"); 116 | let memos = Memos { 117 | path: path.clone(), 118 | inner: vec![ 119 | Memo { 120 | text: "foo".to_string(), 121 | status: Status::Pending, 122 | }, 123 | Memo { 124 | text: "bar".to_string(), 125 | status: Status::Pending, 126 | }, 127 | ], 128 | }; 129 | memos.sync().unwrap(); 130 | let memos_2 = Memos::open(&path).unwrap(); 131 | assert_eq!(memos.inner, memos_2.inner, "wrong data"); 132 | } 133 | 134 | #[test] 135 | fn find_all_fn_returns_all_memos_matching_substring() { 136 | let mut memos = Memos { 137 | path: PathBuf::from("dummy"), 138 | inner: vec![ 139 | Memo { 140 | text: "foo".to_string(), 141 | status: Status::Pending, 142 | }, 143 | Memo { 144 | text: "bar".to_string(), 145 | status: Status::Pending, 146 | }, 147 | Memo { 148 | text: "food".to_string(), 149 | status: Status::Pending, 150 | }, 151 | ], 152 | }; 153 | let found: Vec<&mut Memo> = memos.find_all("foo"); 154 | assert_eq!(found.len(), 2, "wrong number of matches"); 155 | assert_eq!(found[0].text, "foo", "wrong match"); 156 | assert_eq!(found[1].text, "food", "wrong match"); 157 | } 158 | 159 | #[test] 160 | fn purge_done_fn_deletes_all_memos_with_done_status() { 161 | let mut memos = Memos { 162 | path: PathBuf::from("dummy"), 163 | inner: vec![ 164 | Memo { 165 | text: "foo".to_string(), 166 | status: Status::Done, 167 | }, 168 | Memo { 169 | text: "bar".to_string(), 170 | status: Status::Done, 171 | }, 172 | Memo { 173 | text: "food".to_string(), 174 | status: Status::Pending, 175 | }, 176 | ], 177 | }; 178 | memos.purge_done(); 179 | assert_eq!( 180 | memos.inner, 181 | vec![Memo { 182 | text: "food".to_string(), 183 | status: Status::Pending, 184 | },], 185 | "wrong data" 186 | ); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /memo_5/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Parser; 3 | 4 | use memo::{Memo, Memos, Status}; 5 | use memo_5 as memo; 6 | 7 | #[derive(Parser)] 8 | /// Stores and manages simple reminders. 9 | struct Args { 10 | /// Marks all matching memos as done 11 | #[arg(short, long)] 12 | done: bool, 13 | /// Deletes all memos with status “done” 14 | #[arg(short, long)] 15 | purge: bool, 16 | /// Text of the memo to store or mark as done 17 | text: Vec, 18 | } 19 | 20 | fn main() -> Result<()> { 21 | let args = Args::parse(); 22 | let mut memos = Memos::open("memos.json")?; 23 | let text = args.text.join(" "); 24 | if args.purge { 25 | memos.purge_done(); 26 | memos.sync()?; 27 | } 28 | if args.done { 29 | for m in memos.find_all(&text) { 30 | m.status = Status::Done; 31 | println!("Marked \"{}\" as done.", m.text); 32 | } 33 | memos.sync()?; 34 | } else if args.text.is_empty() { 35 | for memo in &memos.inner { 36 | println!("{memo}"); 37 | } 38 | } else { 39 | memos.inner.push(Memo { 40 | text: text.clone(), 41 | status: Status::Pending, 42 | }); 43 | println!("Added \"{}\" as a new memo.", &text); 44 | memos.sync()?; 45 | } 46 | Ok(()) 47 | } 48 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 68 2 | -------------------------------------------------------------------------------- /slim_1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "slim_1" 3 | version = "0.1.0" 4 | edition = "2024" 5 | license = "MIT OR Apache-2.0" 6 | 7 | [dependencies] 8 | anyhow = "1.0.95" 9 | clap = { version = "4.5.26", features = ["derive"] } 10 | walkdir = "2.5.0" 11 | -------------------------------------------------------------------------------- /slim_1/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use walkdir::WalkDir; 3 | 4 | use std::{ 5 | path::{Path, PathBuf}, 6 | process::{Command, Output}, 7 | }; 8 | 9 | /// Runs `cargo clean` on all Rust projects in `path`. 10 | /// 11 | /// # Errors 12 | /// 13 | /// Returns any errors searching `path`, or when running Cargo. 14 | /// 15 | /// # Panics 16 | /// 17 | /// Panics if the target manifest file does not have a parent directory. 18 | /// This should never happen because targets are always `Cargo.toml` files. 19 | pub fn slim(path: impl AsRef) -> Result { 20 | let mut output = String::new(); 21 | for target in manifests(path)? { 22 | let mut cmd = cargo_clean_cmd(&target); 23 | let cmd_output = cmd.output()?; 24 | output.push_str(&summary(target, &cmd_output)); 25 | } 26 | Ok(output) 27 | } 28 | 29 | /// Finds all `Cargo.toml` files in the tree rooted at `path`. 30 | /// 31 | /// # Errors 32 | /// 33 | /// Returns any errors encountered by [`walkdir`]. 34 | fn manifests(path: impl AsRef) -> Result> { 35 | let mut targets = Vec::new(); 36 | for entry in WalkDir::new(path) { 37 | let entry = entry?; 38 | if entry.file_name() == "Cargo.toml" { 39 | targets.push(entry.path().to_path_buf()); 40 | } 41 | } 42 | Ok(targets) 43 | } 44 | 45 | fn cargo_clean_cmd(path: impl AsRef) -> Command { 46 | let mut cmd = Command::new("cargo"); 47 | cmd.args([ 48 | "clean", 49 | "--manifest-path", 50 | &path.as_ref().to_string_lossy(), 51 | ]); 52 | cmd 53 | } 54 | 55 | fn summary(target: impl AsRef, output: &Output) -> String { 56 | format!( 57 | "{}: {}", 58 | target.as_ref().parent().unwrap().display(), 59 | String::from_utf8_lossy(&output.stderr).trim_start() 60 | ) 61 | } 62 | 63 | #[cfg(test)] 64 | mod tests { 65 | use std::{path::PathBuf, process::ExitStatus}; 66 | 67 | use super::*; 68 | 69 | #[test] 70 | fn manifests_returns_cargo_toml_paths() { 71 | let mut manifests = manifests("tests/data").unwrap(); 72 | manifests.sort(); 73 | assert_eq!( 74 | manifests, 75 | vec![ 76 | PathBuf::from("tests/data/proj_1/Cargo.toml"), 77 | PathBuf::from("tests/data/proj_2/Cargo.toml"), 78 | PathBuf::from("tests/data/proj_3/Cargo.toml"), 79 | ], 80 | "wrong paths" 81 | ); 82 | } 83 | 84 | #[test] 85 | fn cargo_clean_cmd_fn_returns_correct_cargo_command() { 86 | let cmd = cargo_clean_cmd(PathBuf::from( 87 | "code/proj_1/Cargo.toml", 88 | )); 89 | assert_eq!(cmd.get_program(), "cargo", "wrong program"); 90 | assert_eq!( 91 | cmd.get_args().collect::>(), 92 | ["clean", "--manifest-path", "code/proj_1/Cargo.toml"], 93 | "wrong args" 94 | ); 95 | } 96 | 97 | #[test] 98 | fn summary_reports_target_path_and_cargo_output() { 99 | let cmd_output = summary( 100 | PathBuf::from("./target/Cargo.toml"), 101 | &Output { 102 | stdout: Vec::new(), 103 | stderr: String::from( 104 | " Removed 2 files, 1.6MiB total\n", 105 | ) 106 | .into_bytes(), 107 | status: ExitStatus::default(), 108 | }, 109 | ); 110 | assert_eq!( 111 | cmd_output, "./target: Removed 2 files, 1.6MiB total\n", 112 | "wrong formatting" 113 | ); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /slim_1/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Parser; 3 | 4 | use slim::slim; 5 | use slim_1 as slim; 6 | 7 | #[derive(Parser)] 8 | /// Runs `cargo clean` recursively to save disk space by deleting build 9 | /// artifacts. 10 | struct Args { 11 | #[arg(default_value = ".")] 12 | /// Path to search for Rust projects. 13 | path: String, 14 | } 15 | 16 | fn main() -> Result<()> { 17 | let args = Args::parse(); 18 | let output = slim(args.path)?; 19 | print!("{output}"); 20 | Ok(()) 21 | } 22 | -------------------------------------------------------------------------------- /slim_1/tests/data/proj_1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "proj_1" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | 8 | [workspace] 9 | -------------------------------------------------------------------------------- /slim_1/tests/data/proj_1/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /slim_1/tests/data/proj_2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "proj_2" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | 8 | [workspace] 9 | -------------------------------------------------------------------------------- /slim_1/tests/data/proj_2/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /slim_1/tests/data/proj_3/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "proj_3" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | 8 | [workspace] 9 | -------------------------------------------------------------------------------- /slim_1/tests/data/proj_3/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /slim_2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "slim_2" 3 | version = "0.1.0" 4 | edition = "2024" 5 | license = "MIT OR Apache-2.0" 6 | 7 | [dependencies] 8 | anyhow = "1.0.95" 9 | clap = { version = "4.5.26", features = ["derive"] } 10 | walkdir = "2.5.0" 11 | -------------------------------------------------------------------------------- /slim_2/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use walkdir::WalkDir; 3 | 4 | use std::{ 5 | path::{Path, PathBuf}, 6 | process::{Command, Output}, 7 | }; 8 | 9 | #[derive(Default)] 10 | pub struct Slimmer { 11 | pub dry_run: bool, 12 | } 13 | 14 | impl Slimmer { 15 | #[must_use] 16 | pub fn new() -> Self { 17 | Self::default() 18 | } 19 | 20 | /// Runs `cargo clean` on all Rust projects in `path`. 21 | /// 22 | /// # Errors 23 | /// 24 | /// Returns any errors searching `path`, or when running Cargo. 25 | /// 26 | /// # Panics 27 | /// 28 | /// Panics if the target manifest file does not have a parent directory. 29 | /// This should never happen because targets are always `Cargo.toml` files. 30 | pub fn slim(&self, path: impl AsRef) -> Result { 31 | let mut output = String::new(); 32 | for target in manifests(path)? { 33 | let mut cmd = self.cargo_clean_cmd(&target); 34 | let cmd_output = cmd.output()?; 35 | output.push_str(&summary(target, &cmd_output)); 36 | } 37 | Ok(output) 38 | } 39 | 40 | fn cargo_clean_cmd(&self, target: impl AsRef) -> Command { 41 | let mut cmd = Command::new("cargo"); 42 | cmd.args([ 43 | "clean", 44 | "--manifest-path", 45 | &target.as_ref().to_string_lossy(), 46 | ]); 47 | if self.dry_run { 48 | cmd.arg("--dry-run"); 49 | } 50 | cmd 51 | } 52 | } 53 | 54 | /// Finds all `Cargo.toml` files in the tree rooted at `path`. 55 | /// 56 | /// # Errors 57 | /// 58 | /// Returns any errors encountered by [`walkdir`]. 59 | fn manifests(path: impl AsRef) -> Result> { 60 | let mut targets = Vec::new(); 61 | for entry in WalkDir::new(path) { 62 | let entry = entry?; 63 | if entry.file_name() == "Cargo.toml" { 64 | targets.push(entry.path().to_path_buf()); 65 | } 66 | } 67 | Ok(targets) 68 | } 69 | 70 | fn summary(target: impl AsRef, output: &Output) -> String { 71 | format!( 72 | "{}: {}", 73 | target.as_ref().parent().unwrap().display(), 74 | String::from_utf8_lossy(&output.stderr).trim_start() 75 | ) 76 | } 77 | 78 | #[cfg(test)] 79 | mod tests { 80 | use std::{path::PathBuf, process::ExitStatus}; 81 | 82 | use super::*; 83 | 84 | #[test] 85 | fn manifests_returns_cargo_toml_paths() { 86 | let mut manifests = manifests("tests/data").unwrap(); 87 | manifests.sort(); 88 | assert_eq!( 89 | manifests, 90 | vec![ 91 | PathBuf::from("tests/data/proj_1/Cargo.toml"), 92 | PathBuf::from("tests/data/proj_2/Cargo.toml"), 93 | PathBuf::from("tests/data/proj_3/Cargo.toml"), 94 | ], 95 | "wrong paths" 96 | ); 97 | } 98 | 99 | #[test] 100 | fn cargo_clean_cmd_fn_returns_correct_cargo_command() { 101 | let slimmer = Slimmer::new(); 102 | let cmd = slimmer.cargo_clean_cmd(PathBuf::from( 103 | "code/proj_1/Cargo.toml", 104 | )); 105 | assert_eq!(cmd.get_program(), "cargo", "wrong program"); 106 | assert_eq!( 107 | cmd.get_args().collect::>(), 108 | ["clean", "--manifest-path", "code/proj_1/Cargo.toml"], 109 | "wrong args" 110 | ); 111 | } 112 | 113 | #[test] 114 | fn cargo_clean_cmd_fn_honours_dry_run_mode() { 115 | let mut slimmer = Slimmer::new(); 116 | slimmer.dry_run = true; 117 | let cmd = slimmer.cargo_clean_cmd("./target/Cargo.toml"); 118 | assert_eq!(cmd.get_program(), "cargo", "wrong program"); 119 | assert_eq!( 120 | cmd.get_args().collect::>(), 121 | vec![ 122 | "clean", 123 | "--manifest-path", 124 | "./target/Cargo.toml", 125 | "--dry-run" 126 | ], 127 | "wrong args" 128 | ); 129 | } 130 | 131 | #[test] 132 | fn summary_reports_target_path_and_cargo_output() { 133 | let cmd_output = summary( 134 | PathBuf::from("./target/Cargo.toml"), 135 | &Output { 136 | stdout: Vec::new(), 137 | stderr: String::from( 138 | " Removed 2 files, 1.6MiB total\n", 139 | ) 140 | .into_bytes(), 141 | status: ExitStatus::default(), 142 | }, 143 | ); 144 | assert_eq!( 145 | cmd_output, "./target: Removed 2 files, 1.6MiB total\n", 146 | "wrong formatting" 147 | ); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /slim_2/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Parser; 3 | 4 | use slim::Slimmer; 5 | use slim_2 as slim; 6 | 7 | #[derive(Parser)] 8 | /// Runs `cargo clean` recursively to save disk space by deleting build 9 | /// artifacts. 10 | struct Args { 11 | #[arg(long)] 12 | /// Don't delete anything, just show what would happen. 13 | dry_run: bool, 14 | #[arg(default_value = ".")] 15 | /// Paths to search for Rust projects. 16 | paths: Vec, 17 | } 18 | 19 | fn main() -> Result<()> { 20 | let args = Args::parse(); 21 | let mut slimmer = Slimmer::new(); 22 | if args.dry_run { 23 | slimmer.dry_run = true; 24 | } 25 | for path in &args.paths { 26 | let output = slimmer.slim(path)?; 27 | print!("{output}"); 28 | } 29 | Ok(()) 30 | } 31 | -------------------------------------------------------------------------------- /slim_2/tests/data/proj_1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "proj_1" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | 8 | [workspace] 9 | -------------------------------------------------------------------------------- /slim_2/tests/data/proj_1/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /slim_2/tests/data/proj_2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "proj_2" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | 8 | [workspace] 9 | -------------------------------------------------------------------------------- /slim_2/tests/data/proj_2/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /slim_2/tests/data/proj_3/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "proj_3" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | 8 | [workspace] 9 | -------------------------------------------------------------------------------- /slim_2/tests/data/proj_3/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /slim_3/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "slim_3" 3 | version = "0.1.0" 4 | edition = "2024" 5 | license = "MIT OR Apache-2.0" 6 | 7 | [[bin]] 8 | name = "cargo-slim" 9 | path = "src/main.rs" 10 | 11 | [dependencies] 12 | anyhow = "1.0.95" 13 | clap = { version = "4.5.26", features = ["derive"] } 14 | walkdir = "2.5.0" 15 | -------------------------------------------------------------------------------- /slim_3/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use walkdir::WalkDir; 3 | 4 | use std::{ 5 | path::{Path, PathBuf}, 6 | process::{Command, Output}, 7 | }; 8 | 9 | #[derive(Default)] 10 | pub struct Slimmer { 11 | pub dry_run: bool, 12 | } 13 | 14 | impl Slimmer { 15 | #[must_use] 16 | pub fn new() -> Self { 17 | Self::default() 18 | } 19 | 20 | /// Runs `cargo clean` on all Rust projects in `path`. 21 | /// 22 | /// # Errors 23 | /// 24 | /// Returns any errors searching `path`, or when running Cargo. 25 | /// 26 | /// # Panics 27 | /// 28 | /// Panics if the target manifest file does not have a parent directory. 29 | /// This should never happen because targets are always `Cargo.toml` files. 30 | pub fn slim(&self, path: impl AsRef) -> Result { 31 | let mut output = String::new(); 32 | for target in manifests(path)? { 33 | let mut cmd = self.cargo_clean_cmd(&target); 34 | let cmd_output = cmd.output()?; 35 | output.push_str(&summary(target, &cmd_output)); 36 | } 37 | Ok(output) 38 | } 39 | 40 | fn cargo_clean_cmd(&self, target: impl AsRef) -> Command { 41 | let mut cmd = Command::new("cargo"); 42 | cmd.args([ 43 | "clean", 44 | "--manifest-path", 45 | &target.as_ref().to_string_lossy(), 46 | ]); 47 | if self.dry_run { 48 | cmd.arg("--dry-run"); 49 | } 50 | cmd 51 | } 52 | } 53 | 54 | /// Finds all `Cargo.toml` files in the tree rooted at `path`. 55 | /// 56 | /// # Errors 57 | /// 58 | /// Returns any errors encountered by [`walkdir`]. 59 | fn manifests(path: impl AsRef) -> Result> { 60 | let mut targets = Vec::new(); 61 | for entry in WalkDir::new(path) 62 | .into_iter() 63 | .filter_entry(|e| !e.path().ends_with("target/package")) 64 | { 65 | let entry = entry?; 66 | if entry.file_name() == "Cargo.toml" { 67 | targets.push(entry.path().to_path_buf()); 68 | } 69 | } 70 | Ok(targets) 71 | } 72 | 73 | fn summary(target: impl AsRef, output: &Output) -> String { 74 | format!( 75 | "{}: {}", 76 | target.as_ref().parent().unwrap().display(), 77 | String::from_utf8_lossy(&output.stderr).trim_start() 78 | ) 79 | } 80 | 81 | #[cfg(test)] 82 | mod tests { 83 | use std::{path::PathBuf, process::ExitStatus}; 84 | 85 | use super::*; 86 | 87 | #[test] 88 | fn manifests_returns_cargo_toml_paths() { 89 | let mut manifests = manifests("tests/data").unwrap(); 90 | manifests.sort(); 91 | assert_eq!( 92 | manifests, 93 | vec![ 94 | PathBuf::from("tests/data/proj_1/Cargo.toml"), 95 | PathBuf::from("tests/data/proj_2/Cargo.toml"), 96 | PathBuf::from("tests/data/proj_3/Cargo.toml"), 97 | ], 98 | "wrong paths" 99 | ); 100 | } 101 | 102 | #[test] 103 | fn cargo_clean_cmd_fn_returns_correct_cargo_command() { 104 | let slimmer = Slimmer::new(); 105 | let cmd = slimmer.cargo_clean_cmd(PathBuf::from( 106 | "code/proj_1/Cargo.toml", 107 | )); 108 | assert_eq!(cmd.get_program(), "cargo", "wrong program"); 109 | assert_eq!( 110 | cmd.get_args().collect::>(), 111 | ["clean", "--manifest-path", "code/proj_1/Cargo.toml"], 112 | "wrong args" 113 | ); 114 | } 115 | 116 | #[test] 117 | fn cargo_clean_cmd_fn_honours_dry_run_mode() { 118 | let mut slimmer = Slimmer::new(); 119 | slimmer.dry_run = true; 120 | let cmd = slimmer.cargo_clean_cmd("./target/Cargo.toml"); 121 | assert_eq!(cmd.get_program(), "cargo", "wrong program"); 122 | assert_eq!( 123 | cmd.get_args().collect::>(), 124 | vec![ 125 | "clean", 126 | "--manifest-path", 127 | "./target/Cargo.toml", 128 | "--dry-run" 129 | ], 130 | "wrong args" 131 | ); 132 | } 133 | 134 | #[test] 135 | fn summary_reports_target_path_and_cargo_output() { 136 | let cmd_output = summary( 137 | PathBuf::from("./target/Cargo.toml"), 138 | &Output { 139 | stdout: Vec::new(), 140 | stderr: String::from( 141 | " Removed 2 files, 1.6MiB total\n", 142 | ) 143 | .into_bytes(), 144 | status: ExitStatus::default(), 145 | }, 146 | ); 147 | assert_eq!( 148 | cmd_output, "./target: Removed 2 files, 1.6MiB total\n", 149 | "wrong formatting" 150 | ); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /slim_3/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Parser; 3 | 4 | use slim::Slimmer; 5 | use slim_3 as slim; 6 | 7 | #[derive(Debug, Parser)] 8 | #[command(bin_name = "cargo")] 9 | enum CargoCommand { 10 | Slim(Args), 11 | } 12 | 13 | #[derive(clap::Args, Debug)] 14 | /// Runs `cargo clean` recursively to save disk space by deleting build 15 | /// artifacts. 16 | struct Args { 17 | #[arg(long)] 18 | /// Don't delete anything, just show what would happen. 19 | dry_run: bool, 20 | #[arg(default_value = ".")] 21 | /// Paths to search for Rust projects. 22 | paths: Vec, 23 | } 24 | 25 | fn main() -> Result<()> { 26 | let CargoCommand::Slim(args) = CargoCommand::parse(); 27 | let mut slimmer = Slimmer::new(); 28 | if args.dry_run { 29 | slimmer.dry_run = true; 30 | } 31 | for path in &args.paths { 32 | let output = slimmer.slim(path)?; 33 | print!("{output}"); 34 | } 35 | Ok(()) 36 | } 37 | -------------------------------------------------------------------------------- /slim_3/tests/data/proj_1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "proj_1" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | 8 | [workspace] 9 | -------------------------------------------------------------------------------- /slim_3/tests/data/proj_1/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /slim_3/tests/data/proj_1/target/package/Cargo.toml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfield/tsr-tools/a17ba0f51a1df247cb90df742d0c2f9208e4ef8d/slim_3/tests/data/proj_1/target/package/Cargo.toml -------------------------------------------------------------------------------- /slim_3/tests/data/proj_2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "proj_2" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | 8 | [workspace] 9 | -------------------------------------------------------------------------------- /slim_3/tests/data/proj_2/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /slim_3/tests/data/proj_3/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "proj_3" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | 8 | [workspace] 9 | -------------------------------------------------------------------------------- /slim_3/tests/data/proj_3/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /timer_1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "timer_1" 3 | version = "0.1.0" 4 | edition = "2024" 5 | license = "MIT OR Apache-2.0" 6 | 7 | [dependencies] 8 | anyhow = "1.0.95" 9 | clap = { version = "4.5.25", features = ["derive"] } 10 | -------------------------------------------------------------------------------- /timer_1/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Parser; 3 | 4 | use std::{process::Command, time::Instant}; 5 | 6 | #[derive(Parser)] 7 | /// Runs a given command and reports elapsed time. 8 | struct Args { 9 | #[arg(required = true)] 10 | /// Name or path of the program to run 11 | program: String, 12 | /// Arguments to the program 13 | args: Vec, 14 | } 15 | 16 | fn main() -> Result<()> { 17 | let args = Args::parse(); 18 | let mut cmd = Command::new(args.program); 19 | cmd.args(args.args); 20 | let start = Instant::now(); 21 | let output = cmd.output()?; 22 | let elapsed = start.elapsed(); 23 | print!("{}", String::from_utf8_lossy(&output.stdout)); 24 | print!("{}", String::from_utf8_lossy(&output.stderr)); 25 | println!("{elapsed:.1?}"); 26 | Ok(()) 27 | } 28 | -------------------------------------------------------------------------------- /timer_2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "timer_2" 3 | version = "0.1.0" 4 | edition = "2024" 5 | license = "MIT OR Apache-2.0" 6 | 7 | [dependencies] 8 | anyhow = "1.0.95" 9 | clap = { version = "4.5.26", features = ["derive"] } 10 | -------------------------------------------------------------------------------- /timer_2/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | use std::{ 4 | process::Command, 5 | time::{Duration, Instant}, 6 | }; 7 | 8 | pub struct Report { 9 | pub stdout: String, 10 | pub stderr: String, 11 | pub elapsed: Duration, 12 | } 13 | 14 | /// Runs `program` with `args`, returning output and elapsed time. 15 | /// 16 | /// # Errors 17 | /// 18 | /// Returns any errors running the command. 19 | pub fn time(program: &str, args: &[String]) -> Result { 20 | let mut cmd = Command::new(program); 21 | cmd.args(args); 22 | let start = Instant::now(); 23 | let output = cmd.output()?; 24 | let elapsed = start.elapsed(); 25 | Ok(Report { 26 | stdout: String::from_utf8_lossy(&output.stdout).to_string(), 27 | stderr: String::from_utf8_lossy(&output.stderr).to_string(), 28 | elapsed, 29 | }) 30 | } 31 | 32 | #[cfg(test)] 33 | mod tests { 34 | use super::*; 35 | 36 | #[test] 37 | fn time_times_echo_cmd() { 38 | let rep = time("echo", &["hey".into()]).unwrap(); 39 | assert_eq!(rep.stdout.trim_end(), "hey", "wrong stdout"); 40 | assert_eq!(rep.stderr.trim_end(), "", "wrong stderr"); 41 | assert!(!rep.elapsed.is_zero(), "zero elapsed time"); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /timer_2/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Parser; 3 | 4 | use timer_2 as timer; 5 | 6 | #[derive(Parser)] 7 | /// Runs a given command and reports elapsed time. 8 | struct Args { 9 | #[arg(required = true)] 10 | /// Name or path of the program to run 11 | program: String, 12 | /// Arguments to the program 13 | args: Vec, 14 | } 15 | 16 | fn main() -> Result<()> { 17 | let args = Args::parse(); 18 | let report = timer::time(&args.program, &args.args)?; 19 | print!("{}", report.stdout); 20 | print!("{}", report.stderr); 21 | println!("{:.1?}", report.elapsed); 22 | Ok(()) 23 | } 24 | -------------------------------------------------------------------------------- /weather_1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "weather_1" 3 | version = "0.1.0" 4 | edition = "2024" 5 | license = "MIT OR Apache-2.0" 6 | 7 | [dependencies] 8 | reqwest = { version = "0.12.9", features = ["blocking"] } 9 | -------------------------------------------------------------------------------- /weather_1/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | fn main() { 4 | let api_key = env::var("WEATHERSTACK_API_KEY").unwrap(); 5 | let resp = reqwest::blocking::Client::new() 6 | .get("https://api.weatherstack.com/current") 7 | .query(&[("query", "London,UK"), ("access_key", &api_key)]) 8 | .send() 9 | .unwrap(); 10 | println!("{}", resp.text().unwrap()); 11 | } 12 | -------------------------------------------------------------------------------- /weather_2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "weather_2" 3 | version = "0.1.0" 4 | edition = "2024" 5 | license = "MIT OR Apache-2.0" 6 | 7 | [dependencies] 8 | anyhow = "1.0.94" 9 | clap = { version = "4.5.23", features = ["derive", "env"] } 10 | reqwest = { version = "0.12.9", features = ["blocking"] } 11 | serde_json = "1.0.133" 12 | url = "2.5.4" 13 | -------------------------------------------------------------------------------- /weather_2/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | 3 | use std::fmt::Display; 4 | 5 | use reqwest::blocking::RequestBuilder; 6 | use serde_json::Value; 7 | 8 | fn request(location: &str, api_key: &str) -> RequestBuilder { 9 | reqwest::blocking::Client::new() 10 | .get("https://api.weatherstack.com/current") 11 | .query(&[("query", location), ("access_key", api_key)]) 12 | } 13 | 14 | fn deserialize(json: &str) -> Result { 15 | let val: Value = serde_json::from_str(json)?; 16 | let temperature = val 17 | .pointer("/current/temperature") 18 | .and_then(Value::as_f64) 19 | .with_context(|| format!("bad response: {val}"))?; 20 | let summary = val 21 | .pointer("/current/weather_descriptions/0") 22 | .and_then(Value::as_str) 23 | .with_context(|| format!("bad response: {val}"))? 24 | .to_string(); 25 | Ok(Weather { 26 | temperature, 27 | summary, 28 | }) 29 | } 30 | 31 | /// Fetches weather data from the Weatherstack API for the given location. 32 | /// 33 | /// # Errors 34 | /// 35 | /// Returns any errors making the request, from the server response, or from 36 | /// deserializing the JSON data. 37 | pub fn get_weather( 38 | location: &str, 39 | api_key: &str, 40 | ) -> Result { 41 | let resp = request(location, api_key).send()?; 42 | let weather = deserialize(&resp.text()?)?; 43 | Ok(weather) 44 | } 45 | 46 | #[derive(Debug, PartialEq)] 47 | pub struct Weather { 48 | temperature: f64, 49 | summary: String, 50 | } 51 | 52 | impl Display for Weather { 53 | fn fmt( 54 | &self, 55 | f: &mut std::fmt::Formatter<'_>, 56 | ) -> std::fmt::Result { 57 | write!(f, "{} {:.1}ºC", self.summary, self.temperature) 58 | } 59 | } 60 | 61 | #[cfg(test)] 62 | mod tests { 63 | use std::fs; 64 | 65 | use super::*; 66 | 67 | use url::Host::Domain; 68 | 69 | #[test] 70 | fn request_builds_correct_request() { 71 | let req = request("London,UK", "dummy API key"); 72 | let req = req.build().unwrap(); 73 | assert_eq!(req.method(), "GET", "wrong method"); 74 | let url = req.url(); 75 | assert_eq!( 76 | url.host(), 77 | Some(Domain("api.weatherstack.com")), 78 | "wrong host" 79 | ); 80 | assert_eq!(url.path(), "/current", "wrong path"); 81 | let params: Vec<(_, _)> = url.query_pairs().collect(); 82 | assert_eq!( 83 | params, 84 | vec![ 85 | ("query".into(), "London,UK".into()), 86 | ("access_key".into(), "dummy API key".into()) 87 | ], 88 | "wrong params" 89 | ); 90 | } 91 | 92 | #[test] 93 | fn deserialize_extracts_correct_weather_from_json() { 94 | let json = 95 | fs::read_to_string("tests/data/ws.json").unwrap(); 96 | let weather = deserialize(&json).unwrap(); 97 | assert_eq!( 98 | weather, 99 | Weather { 100 | temperature: 11.2, 101 | summary: "Sunny".into(), 102 | }, 103 | "wrong weather" 104 | ); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /weather_2/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Parser; 3 | 4 | use weather::get_weather; 5 | use weather_2 as weather; 6 | 7 | #[derive(Parser)] 8 | /// Shows the current weather for a given location. 9 | struct Args { 10 | #[arg( 11 | short, 12 | long, 13 | env = "WEATHERSTACK_API_KEY", 14 | required = true 15 | )] 16 | /// Weatherstack API key 17 | api_key: String, 18 | #[arg(required = true)] 19 | /// Example: "London,UK" 20 | location: Vec, 21 | } 22 | 23 | fn main() -> Result<()> { 24 | let args = Args::parse(); 25 | let location = args.location.join(" "); 26 | let weather = get_weather(&location, &args.api_key)?; 27 | println!("{weather}"); 28 | Ok(()) 29 | } 30 | -------------------------------------------------------------------------------- /weather_2/tests/data/ws.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "type": "City", 4 | "query": "London, United Kingdom", 5 | "language": "en", 6 | "unit": "m" 7 | }, 8 | "location": { 9 | "name": "London", 10 | "country": "United Kingdom", 11 | "region": "City of London, Greater London", 12 | "lat": "51.517", 13 | "lon": "-0.106", 14 | "timezone_id": "Europe/London", 15 | "localtime": "2024-11-29 15:10", 16 | "localtime_epoch": 1732893000, 17 | "utc_offset": "0.0" 18 | }, 19 | "current": { 20 | "observation_time": "03:10 PM", 21 | "temperature": 11.2, 22 | "weather_code": 113, 23 | "weather_icons": [ 24 | "https://cdn.worldweatheronline.com/images/wsymbols01_png_64/wsymbol_0001_sunny.png" 25 | ], 26 | "weather_descriptions": ["Sunny"], 27 | "wind_speed": 16, 28 | "wind_degree": 157, 29 | "wind_dir": "SSE", 30 | "pressure": 1024, 31 | "precip": 0, 32 | "humidity": 54, 33 | "cloudcover": 0, 34 | "feelslike": 10, 35 | "uv_index": 0, 36 | "visibility": 10, 37 | "is_day": "yes" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /weather_3/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "weather_3" 3 | version = "0.1.0" 4 | edition = "2024" 5 | license = "MIT OR Apache-2.0" 6 | 7 | [dependencies] 8 | anyhow = "1.0.95" 9 | clap = { version = "4.5.23", features = ["derive", "env"] } 10 | httpmock = "0.7.0" 11 | reqwest = { version = "0.12.9", features = ["blocking"] } 12 | serde = { version = "1.0.216" } 13 | serde_json = "1.0.134" 14 | url = "2.5.4" 15 | -------------------------------------------------------------------------------- /weather_3/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | 3 | use std::fmt::Display; 4 | 5 | use reqwest::blocking::RequestBuilder; 6 | use serde_json::Value; 7 | 8 | pub struct Weatherstack { 9 | pub base_url: String, 10 | api_key: String, 11 | } 12 | 13 | impl Weatherstack { 14 | #[must_use] 15 | pub fn new(api_key: &str) -> Self { 16 | Self { 17 | base_url: "https://api.weatherstack.com/current".into(), 18 | api_key: api_key.to_owned(), 19 | } 20 | } 21 | 22 | /// Fetches weather data from the Weatherstack API for the given location. 23 | /// 24 | /// # Errors 25 | /// 26 | /// Returns any errors making the request, from the server response, or from 27 | /// deserializing the JSON data. 28 | pub fn get_weather(&self, location: &str) -> Result { 29 | let resp = request(&self.base_url, location, &self.api_key) 30 | .send()?; 31 | let weather = deserialize(&resp.text()?)?; 32 | Ok(weather) 33 | } 34 | } 35 | 36 | fn request( 37 | base_url: &str, 38 | location: &str, 39 | api_key: &str, 40 | ) -> RequestBuilder { 41 | reqwest::blocking::Client::new() 42 | .get(base_url) 43 | .query(&[("query", location), ("access_key", api_key)]) 44 | } 45 | 46 | fn deserialize(json: &str) -> Result { 47 | let val: Value = serde_json::from_str(json)?; 48 | let temperature = val 49 | .pointer("/current/temperature") 50 | .and_then(Value::as_f64) 51 | .with_context(|| format!("bad response: {val}"))?; 52 | let summary = val 53 | .pointer("/current/weather_descriptions/0") 54 | .and_then(Value::as_str) 55 | .with_context(|| format!("bad response: {val}"))? 56 | .to_string(); 57 | Ok(Weather { 58 | temperature, 59 | summary, 60 | }) 61 | } 62 | 63 | #[derive(Debug, PartialEq)] 64 | pub struct Weather { 65 | temperature: f64, 66 | summary: String, 67 | } 68 | 69 | impl Display for Weather { 70 | fn fmt( 71 | &self, 72 | f: &mut std::fmt::Formatter<'_>, 73 | ) -> std::fmt::Result { 74 | write!(f, "{} {:.1}ºC", self.summary, self.temperature) 75 | } 76 | } 77 | 78 | #[cfg(test)] 79 | mod tests { 80 | use std::fs; 81 | 82 | use super::*; 83 | 84 | use url::Host::Domain; 85 | 86 | #[test] 87 | fn request_builds_correct_request() { 88 | let req = request( 89 | "https://example.com/current", 90 | "London,UK", 91 | "dummy API key", 92 | ); 93 | let req = req.build().unwrap(); 94 | assert_eq!(req.method(), "GET", "wrong method"); 95 | let url = req.url(); 96 | assert_eq!( 97 | url.host(), 98 | Some(Domain("example.com")), 99 | "wrong host" 100 | ); 101 | assert_eq!(url.path(), "/current", "wrong path"); 102 | let params: Vec<(_, _)> = url.query_pairs().collect(); 103 | assert_eq!( 104 | params, 105 | vec![ 106 | ("query".into(), "London,UK".into()), 107 | ("access_key".into(), "dummy API key".into()) 108 | ], 109 | "wrong params" 110 | ); 111 | } 112 | 113 | #[test] 114 | fn deserialize_extracts_correct_weather_from_json() { 115 | let json = 116 | fs::read_to_string("tests/data/ws.json").unwrap(); 117 | let weather = deserialize(&json).unwrap(); 118 | assert_eq!( 119 | weather, 120 | Weather { 121 | temperature: 11.2, 122 | summary: "Sunny".into(), 123 | }, 124 | "wrong weather" 125 | ); 126 | } 127 | 128 | use httpmock::{Method, MockServer}; 129 | use reqwest::StatusCode; 130 | 131 | #[test] 132 | fn get_weather_fn_makes_correct_api_call() { 133 | let server = MockServer::start(); 134 | let mock = server.mock(|when, then| { 135 | when.method(Method::GET) 136 | .path("/current") 137 | .query_param("query", "London,UK") 138 | .query_param("access_key", "dummy api key"); 139 | then.status(StatusCode::OK.into()) 140 | .header("content-type", "application/json") 141 | .body_from_file("tests/data/ws.json"); 142 | }); 143 | let mut ws = Weatherstack::new("dummy api key"); 144 | ws.base_url = server.base_url() + "/current"; 145 | let weather = ws.get_weather("London,UK"); 146 | mock.assert(); 147 | assert_eq!( 148 | weather.unwrap(), 149 | Weather { 150 | temperature: 11.2, 151 | summary: "Sunny".into(), 152 | }, 153 | "wrong weather" 154 | ); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /weather_3/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Parser; 3 | 4 | use weather::Weatherstack; 5 | use weather_3 as weather; 6 | 7 | #[derive(Parser)] 8 | /// Shows the current weather for a given location. 9 | struct Args { 10 | #[arg( 11 | short, 12 | long, 13 | env = "WEATHERSTACK_API_KEY", 14 | required = true 15 | )] 16 | /// Weatherstack API key 17 | api_key: String, 18 | #[arg(required = true)] 19 | /// Example: "London,UK" 20 | location: Vec, 21 | } 22 | 23 | fn main() -> Result<()> { 24 | let args = Args::parse(); 25 | let location = args.location.join(" "); 26 | let ws = Weatherstack::new(&args.api_key); 27 | let weather = ws.get_weather(&location)?; 28 | println!("{weather}"); 29 | Ok(()) 30 | } 31 | -------------------------------------------------------------------------------- /weather_3/tests/data/ws.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "type": "City", 4 | "query": "London, United Kingdom", 5 | "language": "en", 6 | "unit": "m" 7 | }, 8 | "location": { 9 | "name": "London", 10 | "country": "United Kingdom", 11 | "region": "City of London, Greater London", 12 | "lat": "51.517", 13 | "lon": "-0.106", 14 | "timezone_id": "Europe/London", 15 | "localtime": "2024-11-29 15:10", 16 | "localtime_epoch": 1732893000, 17 | "utc_offset": "0.0" 18 | }, 19 | "current": { 20 | "observation_time": "03:10 PM", 21 | "temperature": 11.2, 22 | "weather_code": 113, 23 | "weather_icons": [ 24 | "https://cdn.worldweatheronline.com/images/wsymbols01_png_64/wsymbol_0001_sunny.png" 25 | ], 26 | "weather_descriptions": ["Sunny"], 27 | "wind_speed": 16, 28 | "wind_degree": 157, 29 | "wind_dir": "SSE", 30 | "pressure": 1024, 31 | "precip": 0, 32 | "humidity": 54, 33 | "cloudcover": 0, 34 | "feelslike": 10, 35 | "uv_index": 0, 36 | "visibility": 10, 37 | "is_day": "yes" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /weather_4/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "weather_4" 3 | version = "0.1.0" 4 | edition = "2024" 5 | license = "MIT OR Apache-2.0" 6 | 7 | [dependencies] 8 | anyhow = "1.0.95" 9 | clap = { version = "4.5.23", features = ["derive", "env"] } 10 | httpmock = "0.7.0" 11 | reqwest = { version = "0.12.9", features = ["blocking"] } 12 | serde = "1.0.216" 13 | serde_json = "1.0.134" 14 | url = "2.5.4" 15 | -------------------------------------------------------------------------------- /weather_4/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | 3 | use reqwest::blocking::RequestBuilder; 4 | use serde_json::Value; 5 | 6 | pub struct Weatherstack { 7 | pub base_url: String, 8 | api_key: String, 9 | } 10 | 11 | impl Weatherstack { 12 | #[must_use] 13 | pub fn new(api_key: &str) -> Self { 14 | Self { 15 | base_url: "https://api.weatherstack.com/current".into(), 16 | api_key: api_key.to_owned(), 17 | } 18 | } 19 | 20 | /// Fetches weather data from the Weatherstack API for the given location. 21 | /// 22 | /// # Errors 23 | /// 24 | /// Returns any errors making the request, from the server response, or from 25 | /// deserializing the JSON data. 26 | pub fn get_weather(&self, location: &str) -> Result { 27 | let resp = request(&self.base_url, location, &self.api_key) 28 | .send()?; 29 | let weather = deserialize(&resp.text()?)?; 30 | Ok(weather) 31 | } 32 | } 33 | 34 | fn request( 35 | base_url: &str, 36 | location: &str, 37 | api_key: &str, 38 | ) -> RequestBuilder { 39 | reqwest::blocking::Client::new() 40 | .get(base_url) 41 | .query(&[("query", location), ("access_key", api_key)]) 42 | } 43 | 44 | fn deserialize(json: &str) -> Result { 45 | let val: Value = serde_json::from_str(json)?; 46 | let temperature = val 47 | .pointer("/current/temperature") 48 | .and_then(Value::as_f64) 49 | .with_context(|| format!("bad response: {val}"))?; 50 | let summary = val 51 | .pointer("/current/weather_descriptions/0") 52 | .and_then(Value::as_str) 53 | .with_context(|| format!("bad response: {val}"))? 54 | .to_string(); 55 | Ok(Weather { 56 | temperature: Temperature::from_celsius(temperature), 57 | summary, 58 | }) 59 | } 60 | 61 | #[derive(Debug, PartialEq)] 62 | pub struct Temperature(f64); 63 | 64 | impl Temperature { 65 | #[must_use] 66 | pub fn from_celsius(val: f64) -> Self { 67 | Self(val) 68 | } 69 | 70 | #[must_use] 71 | pub fn as_celsius(&self) -> f64 { 72 | self.0 73 | } 74 | 75 | #[must_use] 76 | pub fn as_fahrenheit(&self) -> f64 { 77 | self.0 * 1.8 + 32.0 78 | } 79 | } 80 | 81 | #[derive(Debug, PartialEq)] 82 | pub struct Weather { 83 | pub temperature: Temperature, 84 | pub summary: String, 85 | } 86 | 87 | #[cfg(test)] 88 | mod tests { 89 | use std::fs; 90 | 91 | use super::*; 92 | 93 | use url::Host::Domain; 94 | 95 | #[test] 96 | fn request_builds_correct_request() { 97 | let req = request( 98 | "https://example.com/current", 99 | "London,UK", 100 | "dummy API key", 101 | ); 102 | let req = req.build().unwrap(); 103 | assert_eq!(req.method(), "GET", "wrong method"); 104 | let url = req.url(); 105 | assert_eq!( 106 | url.host(), 107 | Some(Domain("example.com")), 108 | "wrong host" 109 | ); 110 | assert_eq!(url.path(), "/current", "wrong path"); 111 | let params: Vec<(_, _)> = url.query_pairs().collect(); 112 | assert_eq!( 113 | params, 114 | vec![ 115 | ("query".into(), "London,UK".into()), 116 | ("access_key".into(), "dummy API key".into()) 117 | ], 118 | "wrong params" 119 | ); 120 | } 121 | 122 | #[test] 123 | fn deserialize_extracts_correct_weather_from_json() { 124 | let json = 125 | fs::read_to_string("tests/data/ws.json").unwrap(); 126 | let weather = deserialize(&json).unwrap(); 127 | assert_eq!( 128 | weather, 129 | Weather { 130 | temperature: Temperature::from_celsius(11.2), 131 | summary: "Sunny".into(), 132 | }, 133 | "wrong weather" 134 | ); 135 | } 136 | 137 | use httpmock::{Method, MockServer}; 138 | use reqwest::StatusCode; 139 | 140 | #[test] 141 | fn get_weather_fn_makes_correct_api_call() { 142 | let server = MockServer::start(); 143 | let mock = server.mock(|when, then| { 144 | when.method(Method::GET) 145 | .path("/current") 146 | .query_param("query", "London,UK") 147 | .query_param("access_key", "dummy api key"); 148 | then.status(StatusCode::OK.into()) 149 | .header("content-type", "application/json") 150 | .body_from_file("tests/data/ws.json"); 151 | }); 152 | let mut ws = Weatherstack::new("dummy api key"); 153 | ws.base_url = server.base_url() + "/current"; 154 | let weather = ws.get_weather("London,UK"); 155 | mock.assert(); 156 | assert_eq!( 157 | weather.unwrap(), 158 | Weather { 159 | temperature: Temperature(11.2), 160 | summary: "Sunny".into(), 161 | }, 162 | "wrong weather" 163 | ); 164 | } 165 | 166 | #[test] 167 | #[allow(clippy::float_cmp)] 168 | fn temperature_can_be_expressed_as_celsius_or_fahrenheit() { 169 | let temp = Temperature::from_celsius(10.0); 170 | assert_eq!(temp.as_celsius(), 10.0, "wrong celsius"); 171 | assert_eq!(temp.as_fahrenheit(), 50.0, "wrong fahrenheit"); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /weather_4/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Parser; 3 | 4 | use weather::Weatherstack; 5 | use weather_4 as weather; 6 | 7 | #[derive(Parser)] 8 | /// Shows the current weather for a given location. 9 | struct Args { 10 | #[arg( 11 | short, 12 | long, 13 | env = "WEATHERSTACK_API_KEY", 14 | required = true 15 | )] 16 | /// Weatherstack API key 17 | api_key: String, 18 | #[arg(short, long)] 19 | /// Report temperatures in Fahrenheit 20 | fahrenheit: bool, 21 | #[arg(required = true)] 22 | /// Example: "London,UK" 23 | location: Vec, 24 | } 25 | 26 | fn main() -> Result<()> { 27 | let args = Args::parse(); 28 | let location = args.location.join(" "); 29 | let ws = Weatherstack::new(&args.api_key); 30 | let weather = ws.get_weather(&location)?; 31 | println!( 32 | "{} {}", 33 | weather.summary, 34 | if args.fahrenheit { 35 | format!("{:.1}ºF", weather.temperature.as_fahrenheit()) 36 | } else { 37 | format!("{:.1}ºC", weather.temperature.as_celsius()) 38 | } 39 | ); 40 | Ok(()) 41 | } 42 | -------------------------------------------------------------------------------- /weather_4/tests/data/ws.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "type": "City", 4 | "query": "London, United Kingdom", 5 | "language": "en", 6 | "unit": "m" 7 | }, 8 | "location": { 9 | "name": "London", 10 | "country": "United Kingdom", 11 | "region": "City of London, Greater London", 12 | "lat": "51.517", 13 | "lon": "-0.106", 14 | "timezone_id": "Europe/London", 15 | "localtime": "2024-11-29 15:10", 16 | "localtime_epoch": 1732893000, 17 | "utc_offset": "0.0" 18 | }, 19 | "current": { 20 | "observation_time": "03:10 PM", 21 | "temperature": 11.2, 22 | "weather_code": 113, 23 | "weather_icons": [ 24 | "https://cdn.worldweatheronline.com/images/wsymbols01_png_64/wsymbol_0001_sunny.png" 25 | ], 26 | "weather_descriptions": ["Sunny"], 27 | "wind_speed": 16, 28 | "wind_degree": 157, 29 | "wind_dir": "SSE", 30 | "pressure": 1024, 31 | "precip": 0, 32 | "humidity": 54, 33 | "cloudcover": 0, 34 | "feelslike": 10, 35 | "uv_index": 0, 36 | "visibility": 10, 37 | "is_day": "yes" 38 | } 39 | } 40 | --------------------------------------------------------------------------------