├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md └── src └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anstream" 7 | version = "0.2.6" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "342258dd14006105c2b75ab1bd7543a03bdf0cfc94383303ac212a04939dff6f" 10 | dependencies = [ 11 | "anstyle", 12 | "anstyle-parse", 13 | "anstyle-wincon", 14 | "concolor-override", 15 | "concolor-query", 16 | "is-terminal", 17 | "utf8parse", 18 | ] 19 | 20 | [[package]] 21 | name = "anstyle" 22 | version = "0.3.5" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "23ea9e81bd02e310c216d080f6223c179012256e5151c41db88d12c88a1684d2" 25 | 26 | [[package]] 27 | name = "anstyle-parse" 28 | version = "0.1.1" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "a7d1bb534e9efed14f3e5f44e7dd1a4f709384023a4165199a4241e18dff0116" 31 | dependencies = [ 32 | "utf8parse", 33 | ] 34 | 35 | [[package]] 36 | name = "anstyle-wincon" 37 | version = "0.2.0" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "c3127af6145b149f3287bb9a0d10ad9c5692dba8c53ad48285e5bec4063834fa" 40 | dependencies = [ 41 | "anstyle", 42 | "windows-sys 0.45.0", 43 | ] 44 | 45 | [[package]] 46 | name = "autocfg" 47 | version = "1.1.0" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 50 | 51 | [[package]] 52 | name = "bitflags" 53 | version = "1.3.2" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 56 | 57 | [[package]] 58 | name = "cc" 59 | version = "1.0.79" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" 62 | 63 | [[package]] 64 | name = "chrono" 65 | version = "0.4.19" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 68 | dependencies = [ 69 | "libc", 70 | "num-integer", 71 | "num-traits", 72 | "time", 73 | "winapi", 74 | ] 75 | 76 | [[package]] 77 | name = "clap" 78 | version = "4.2.1" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "046ae530c528f252094e4a77886ee1374437744b2bff1497aa898bbddbbb29b3" 81 | dependencies = [ 82 | "clap_builder", 83 | "clap_derive", 84 | "once_cell", 85 | ] 86 | 87 | [[package]] 88 | name = "clap_builder" 89 | version = "4.2.1" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "223163f58c9a40c3b0a43e1c4b50a9ce09f007ea2cb1ec258a687945b4b7929f" 92 | dependencies = [ 93 | "anstream", 94 | "anstyle", 95 | "bitflags", 96 | "clap_lex", 97 | "strsim", 98 | ] 99 | 100 | [[package]] 101 | name = "clap_derive" 102 | version = "4.2.0" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" 105 | dependencies = [ 106 | "heck", 107 | "proc-macro2", 108 | "quote", 109 | "syn", 110 | ] 111 | 112 | [[package]] 113 | name = "clap_lex" 114 | version = "0.4.1" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" 117 | 118 | [[package]] 119 | name = "concolor-override" 120 | version = "1.0.0" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "a855d4a1978dc52fb0536a04d384c2c0c1aa273597f08b77c8c4d3b2eec6037f" 123 | 124 | [[package]] 125 | name = "concolor-query" 126 | version = "0.3.3" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf" 129 | dependencies = [ 130 | "windows-sys 0.45.0", 131 | ] 132 | 133 | [[package]] 134 | name = "console" 135 | version = "0.15.0" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "a28b32d32ca44b70c3e4acd7db1babf555fa026e385fb95f18028f88848b3c31" 138 | dependencies = [ 139 | "encode_unicode", 140 | "libc", 141 | "once_cell", 142 | "regex", 143 | "terminal_size", 144 | "unicode-width", 145 | "winapi", 146 | ] 147 | 148 | [[package]] 149 | name = "encode_unicode" 150 | version = "0.3.6" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 153 | 154 | [[package]] 155 | name = "errno" 156 | version = "0.3.0" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0" 159 | dependencies = [ 160 | "errno-dragonfly", 161 | "libc", 162 | "windows-sys 0.45.0", 163 | ] 164 | 165 | [[package]] 166 | name = "errno-dragonfly" 167 | version = "0.1.2" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 170 | dependencies = [ 171 | "cc", 172 | "libc", 173 | ] 174 | 175 | [[package]] 176 | name = "heck" 177 | version = "0.4.1" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 180 | 181 | [[package]] 182 | name = "hermit-abi" 183 | version = "0.3.1" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" 186 | 187 | [[package]] 188 | name = "indicatif" 189 | version = "0.17.3" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "cef509aa9bc73864d6756f0d34d35504af3cf0844373afe9b8669a5b8005a729" 192 | dependencies = [ 193 | "console", 194 | "number_prefix", 195 | "portable-atomic", 196 | "unicode-width", 197 | ] 198 | 199 | [[package]] 200 | name = "io-lifetimes" 201 | version = "1.0.10" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" 204 | dependencies = [ 205 | "hermit-abi", 206 | "libc", 207 | "windows-sys 0.48.0", 208 | ] 209 | 210 | [[package]] 211 | name = "is-terminal" 212 | version = "0.4.7" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" 215 | dependencies = [ 216 | "hermit-abi", 217 | "io-lifetimes", 218 | "rustix", 219 | "windows-sys 0.48.0", 220 | ] 221 | 222 | [[package]] 223 | name = "libc" 224 | version = "0.2.141" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" 227 | 228 | [[package]] 229 | name = "linux-raw-sys" 230 | version = "0.3.1" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" 233 | 234 | [[package]] 235 | name = "num-integer" 236 | version = "0.1.45" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 239 | dependencies = [ 240 | "autocfg", 241 | "num-traits", 242 | ] 243 | 244 | [[package]] 245 | name = "num-traits" 246 | version = "0.2.15" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 249 | dependencies = [ 250 | "autocfg", 251 | ] 252 | 253 | [[package]] 254 | name = "number_prefix" 255 | version = "0.4.0" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" 258 | 259 | [[package]] 260 | name = "once_cell" 261 | version = "1.12.0" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" 264 | 265 | [[package]] 266 | name = "portable-atomic" 267 | version = "0.3.19" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "26f6a7b87c2e435a3241addceeeff740ff8b7e76b74c13bf9acb17fa454ea00b" 270 | 271 | [[package]] 272 | name = "proc-macro2" 273 | version = "1.0.56" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" 276 | dependencies = [ 277 | "unicode-ident", 278 | ] 279 | 280 | [[package]] 281 | name = "pv" 282 | version = "0.3.0" 283 | dependencies = [ 284 | "chrono", 285 | "clap", 286 | "indicatif", 287 | ] 288 | 289 | [[package]] 290 | name = "quote" 291 | version = "1.0.26" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" 294 | dependencies = [ 295 | "proc-macro2", 296 | ] 297 | 298 | [[package]] 299 | name = "regex" 300 | version = "1.5.6" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" 303 | dependencies = [ 304 | "regex-syntax", 305 | ] 306 | 307 | [[package]] 308 | name = "regex-syntax" 309 | version = "0.6.26" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" 312 | 313 | [[package]] 314 | name = "rustix" 315 | version = "0.37.8" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "1aef160324be24d31a62147fae491c14d2204a3865c7ca8c3b0d7f7bcb3ea635" 318 | dependencies = [ 319 | "bitflags", 320 | "errno", 321 | "io-lifetimes", 322 | "libc", 323 | "linux-raw-sys", 324 | "windows-sys 0.48.0", 325 | ] 326 | 327 | [[package]] 328 | name = "strsim" 329 | version = "0.10.0" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 332 | 333 | [[package]] 334 | name = "syn" 335 | version = "2.0.13" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" 338 | dependencies = [ 339 | "proc-macro2", 340 | "quote", 341 | "unicode-ident", 342 | ] 343 | 344 | [[package]] 345 | name = "terminal_size" 346 | version = "0.1.17" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" 349 | dependencies = [ 350 | "libc", 351 | "winapi", 352 | ] 353 | 354 | [[package]] 355 | name = "time" 356 | version = "0.1.44" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 359 | dependencies = [ 360 | "libc", 361 | "wasi", 362 | "winapi", 363 | ] 364 | 365 | [[package]] 366 | name = "unicode-ident" 367 | version = "1.0.8" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" 370 | 371 | [[package]] 372 | name = "unicode-width" 373 | version = "0.1.9" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 376 | 377 | [[package]] 378 | name = "utf8parse" 379 | version = "0.2.1" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 382 | 383 | [[package]] 384 | name = "wasi" 385 | version = "0.10.0+wasi-snapshot-preview1" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 388 | 389 | [[package]] 390 | name = "winapi" 391 | version = "0.3.9" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 394 | dependencies = [ 395 | "winapi-i686-pc-windows-gnu", 396 | "winapi-x86_64-pc-windows-gnu", 397 | ] 398 | 399 | [[package]] 400 | name = "winapi-i686-pc-windows-gnu" 401 | version = "0.4.0" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 404 | 405 | [[package]] 406 | name = "winapi-x86_64-pc-windows-gnu" 407 | version = "0.4.0" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 410 | 411 | [[package]] 412 | name = "windows-sys" 413 | version = "0.45.0" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 416 | dependencies = [ 417 | "windows-targets 0.42.2", 418 | ] 419 | 420 | [[package]] 421 | name = "windows-sys" 422 | version = "0.48.0" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 425 | dependencies = [ 426 | "windows-targets 0.48.0", 427 | ] 428 | 429 | [[package]] 430 | name = "windows-targets" 431 | version = "0.42.2" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 434 | dependencies = [ 435 | "windows_aarch64_gnullvm 0.42.2", 436 | "windows_aarch64_msvc 0.42.2", 437 | "windows_i686_gnu 0.42.2", 438 | "windows_i686_msvc 0.42.2", 439 | "windows_x86_64_gnu 0.42.2", 440 | "windows_x86_64_gnullvm 0.42.2", 441 | "windows_x86_64_msvc 0.42.2", 442 | ] 443 | 444 | [[package]] 445 | name = "windows-targets" 446 | version = "0.48.0" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" 449 | dependencies = [ 450 | "windows_aarch64_gnullvm 0.48.0", 451 | "windows_aarch64_msvc 0.48.0", 452 | "windows_i686_gnu 0.48.0", 453 | "windows_i686_msvc 0.48.0", 454 | "windows_x86_64_gnu 0.48.0", 455 | "windows_x86_64_gnullvm 0.48.0", 456 | "windows_x86_64_msvc 0.48.0", 457 | ] 458 | 459 | [[package]] 460 | name = "windows_aarch64_gnullvm" 461 | version = "0.42.2" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 464 | 465 | [[package]] 466 | name = "windows_aarch64_gnullvm" 467 | version = "0.48.0" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" 470 | 471 | [[package]] 472 | name = "windows_aarch64_msvc" 473 | version = "0.42.2" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 476 | 477 | [[package]] 478 | name = "windows_aarch64_msvc" 479 | version = "0.48.0" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" 482 | 483 | [[package]] 484 | name = "windows_i686_gnu" 485 | version = "0.42.2" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 488 | 489 | [[package]] 490 | name = "windows_i686_gnu" 491 | version = "0.48.0" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" 494 | 495 | [[package]] 496 | name = "windows_i686_msvc" 497 | version = "0.42.2" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 500 | 501 | [[package]] 502 | name = "windows_i686_msvc" 503 | version = "0.48.0" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" 506 | 507 | [[package]] 508 | name = "windows_x86_64_gnu" 509 | version = "0.42.2" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 512 | 513 | [[package]] 514 | name = "windows_x86_64_gnu" 515 | version = "0.48.0" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" 518 | 519 | [[package]] 520 | name = "windows_x86_64_gnullvm" 521 | version = "0.42.2" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 524 | 525 | [[package]] 526 | name = "windows_x86_64_gnullvm" 527 | version = "0.48.0" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" 530 | 531 | [[package]] 532 | name = "windows_x86_64_msvc" 533 | version = "0.42.2" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 536 | 537 | [[package]] 538 | name = "windows_x86_64_msvc" 539 | version = "0.48.0" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" 542 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pv" 3 | version = "0.3.0" 4 | authors = ["Sean Gallagher "] 5 | edition = "2018" 6 | description = "Rust reimplementation of the unix pipeview (pv) utility" 7 | license = "MIT" 8 | documentation = "https://docs.rs/pv" 9 | homepage = "https://github.com/SeanTater/pv" 10 | repository = "https://github.com/SeanTater/pv" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | clap = { version = "^4.2", features = [ "derive" ] } 16 | chrono = "^0.4" 17 | indicatif = "^0.17" 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Sean Gallagher 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 | # Pipe viewer reimplementation 2 | `pv` is a Unix pipe monitoring application. (And this is copy of the much older original) 3 | 4 | You can use it in places where a progressbar, or at least a flow rate meter, 5 | would be handy. Some handy examples: 6 | 7 | ```sh 8 | # Is it still transferring or did something freeze? 9 | docker save excelsior | pv | ssh me@devbox.company.com "docker load" 10 | ``` 11 | 12 | ```sh 13 | # Why doesn't gzip have a progressbar already? 14 | pv gigantic-file | gunzip | gawk '/size/ { x += $4 } END {print x}' 15 | ``` -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use indicatif::{ProgressBar, ProgressStyle}; 3 | use std::fs::File; 4 | use std::io; 5 | use std::io::{ErrorKind, Read, Write}; 6 | use std::time::Duration; 7 | 8 | const DEFAULT_BUF_SIZE: usize = 65536; 9 | 10 | #[derive(Parser, Debug)] 11 | struct PipeViewConfig { 12 | /// Set estimated data size to SIZE bytes 13 | #[arg(short = 's')] 14 | size: Option, 15 | /// Show elapsed time 16 | #[arg(short = 't')] 17 | timer: bool, 18 | /// Width of the progressbar (default: max) 19 | #[arg(short = 'w')] 20 | width: Option, 21 | /// Show number of bytes transferred 22 | #[arg(short = 'b')] 23 | bytes: bool, 24 | /// Show data transfer rate counter 25 | #[arg(short = 'r')] 26 | rate: bool, 27 | /// Show data transfer average rate counter (same as rate in this implementation, for now) 28 | #[arg(short = 'a')] 29 | average_rate: bool, 30 | /// Show estimated time of arrival (completion) 31 | #[arg(short = 'e')] 32 | eta: bool, 33 | /// Show absolute estimated time of arrival (completion) (same as fineta in this implementation, for now) 34 | #[arg(short = 'I')] 35 | fineta: bool, 36 | /// Count lines instead of bytes 37 | #[arg(short = 'l')] 38 | line_mode: bool, 39 | /// Lines are null-terminated 40 | #[arg(short = '0')] 41 | null: bool, 42 | /// Skip read errors in input 43 | #[arg(short = 'E')] 44 | skip_input_errors: bool, 45 | /// Skip read errors in output 46 | #[arg(short = 'O')] 47 | skip_output_errors: bool, 48 | /// Input filenames. Use -, /dev/stdin, or nothing, to use stdin 49 | #[arg(short = 'f')] 50 | input_filenames: Vec, 51 | /// Show message every N seconds instead of once per block (useful for high throughput streams) 52 | #[arg(short = 'i')] 53 | interval: Option, 54 | /// Prefix the bar with this message 55 | #[arg(short = 'N')] 56 | name: Option, 57 | /// Ignored for compatibility 58 | #[arg(short = 'T')] 59 | buffer_percent: bool, 60 | /// Ignored for compatibility 61 | #[arg(short = 'B')] 62 | buffer_size: Option, 63 | /// Ignored for compatibility; if you want "quiet", don't use pv 64 | #[arg(short = 'q')] 65 | quiet: bool, 66 | /// Ignored for compatibility; this implementation always shows the progressbar 67 | #[arg(short = 'p')] 68 | progress: bool, 69 | /// Ignored for compatibility 70 | #[arg(short = 'H')] 71 | height: Option, 72 | } 73 | 74 | fn main() { 75 | let mut matches = PipeViewConfig::parse(); 76 | 77 | // Guess an expected size if possible 78 | matches.size = Some(matches.size.unwrap_or(matches 79 | .input_filenames 80 | .iter() 81 | .map(|fname| File::open(fname).expect("Failed to open file").metadata().expect("Could not stat file").len()) 82 | .sum())); 83 | 84 | let sources = if matches.input_filenames.is_empty() { 85 | Box::new(io::stdin()) as Box 86 | } else { 87 | matches 88 | .input_filenames 89 | .iter() 90 | // Beware a lot of boxing coming up 91 | .map(|fname| match fname.as_str() { 92 | // Interpret - as stdin 93 | "-" => Box::new(io::stdin()) as Box, 94 | _ => Box::new(File::open(fname).expect("Failed to open file")) as Box, 95 | }) 96 | // Concatenate the files 97 | .fold(Box::new(io::empty()) as Box, |ch, f| { 98 | Box::new(ch.chain(f)) as Box 99 | }) 100 | }; 101 | 102 | PipeView { 103 | source: sources, // Source 104 | sink: Box::new(io::BufWriter::new(io::stdout())), // Sink 105 | progress: PipeView::progress_from_options( 106 | &matches 107 | ), 108 | line_mode: if matches.line_mode { 109 | LineMode::Line(if matches.null { 0 } else { 10 }) // default to unix newline 110 | } else { 111 | LineMode::Byte 112 | }, 113 | skip_input_errors: matches.skip_input_errors, 114 | skip_output_errors: matches.skip_output_errors, 115 | } 116 | .pipeview() 117 | .unwrap(); 118 | } 119 | 120 | /// Prevent a bunch of boxing noise by forcing a cast 121 | 122 | enum LineMode { 123 | Line(u8), 124 | Byte, 125 | } 126 | struct PipeView { 127 | source: Box, 128 | sink: Box, 129 | progress: ProgressBar, 130 | line_mode: LineMode, 131 | skip_input_errors: bool, 132 | skip_output_errors: bool, 133 | } 134 | 135 | impl PipeView { 136 | /// Set up the progress bar from the parsed CLI options 137 | fn progress_from_options( 138 | conf: &PipeViewConfig, 139 | ) -> ProgressBar { 140 | // What to show, from left to right, in the progress bar 141 | let mut template = vec![]; 142 | 143 | if let Some(ref msg) = conf.name { 144 | template.push(msg.to_string()); 145 | } 146 | if conf.timer { 147 | template.push("{elapsed_precise}".to_string()); 148 | } 149 | 150 | match conf.width { 151 | Some(x) => template.push(format!("{{bar:{x}}} {{percent}}")), 152 | None => template.push("{wide_bar} {percent}%".to_string()), 153 | } 154 | 155 | // Choose whether you want bytes or plain counts on several fields 156 | let (pos_name, len_name, per_sec_name) = if conf.line_mode { 157 | ("{pos}", "{len}", "{per_sec}") 158 | } else { 159 | ("{bytes}", "{total_bytes}", "{bytes_per_sec}") 160 | }; 161 | 162 | // Put the transferred and total together so they don't have a space 163 | if conf.bytes && conf.size.is_some() { 164 | template.push(format!("{pos_name}/{len_name}")); 165 | } else if conf.bytes { 166 | template.push(pos_name.to_string()); 167 | } 168 | 169 | if conf.rate || conf.average_rate { 170 | template.push(per_sec_name.to_string()); 171 | } 172 | 173 | if conf.eta || conf.fineta { 174 | template.push("{eta_precise}".to_string()); 175 | } 176 | 177 | let mut style = match conf.size { 178 | Some(_x) => ProgressStyle::default_bar(), 179 | None => ProgressStyle::default_spinner(), 180 | }; 181 | 182 | // Okay, that's all fine and dandy but if they don't specify anything, 183 | // we should have a nicer default than all empty 184 | if !(conf.timer || conf.bytes || conf.rate || conf.average_rate || conf.eta || conf.fineta) { 185 | style = style.template(&format!( 186 | "{{elapsed}} {{wide_bar}} {{percent}}% {pos_name}/{len_name} {per_sec_name} {{eta}}" 187 | )).unwrap(); 188 | } else { 189 | style = style.template(&template.join(" ")).unwrap(); 190 | } 191 | 192 | let progress = match conf.size { 193 | Some(x) => ProgressBar::new(x), 194 | None => ProgressBar::new_spinner(), 195 | }; 196 | 197 | // Optionally enable steady tick 198 | if let Some(sec) = conf.interval { 199 | progress.enable_steady_tick(Duration::from_secs_f64(sec)); 200 | } 201 | progress.set_style(style); 202 | progress 203 | } 204 | 205 | fn pipeview(&mut self) -> Result> { 206 | // Essentially std::io::copy 207 | let mut buf = [0; DEFAULT_BUF_SIZE]; 208 | let mut written: u64 = 0; 209 | loop { 210 | // Always skip interruptions, maybe skip other errors 211 | // Also maybe finish if we read nothing 212 | let len = match self.source.read(&mut buf) { 213 | Ok(0) => return Ok(written), 214 | Ok(len) => len, 215 | Err(ref e) if e.kind() == ErrorKind::Interrupted => continue, 216 | Err(_) if self.skip_input_errors => continue, 217 | Err(e) => return Err(e.into()), 218 | }; 219 | 220 | // Maybe skip output errors 221 | match self.sink.write_all(&buf[..len]) { 222 | Ok(_) => (), 223 | Err(_) if self.skip_output_errors => continue, 224 | Err(e) => return Err(e.into()), 225 | }; 226 | match self.line_mode { 227 | LineMode::Line(delim) => self 228 | .progress 229 | .inc(buf[..len].iter().filter(|b| **b == delim).count() as u64), 230 | LineMode::Byte => self.progress.inc(len as u64), 231 | }; 232 | written += len as u64; 233 | } 234 | } 235 | } 236 | --------------------------------------------------------------------------------