indicatif-0.17.7/.github/dependabot.yml000064400000000000000000000002651046102023000160450ustar 00000000000000version: 2 updates: - package-ecosystem: cargo directory: "/" schedule: interval: daily - package-ecosystem: github-actions directory: "/" schedule: interval: daily indicatif-0.17.7/.github/workflows/rust.yml000064400000000000000000000037361046102023000170000ustar 00000000000000name: CI on: push: branches: ['main'] pull_request: schedule: - cron: "43 6 * * 5" jobs: test: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] rust: [stable] features: [--all-features] target: [""] include: - os: ubuntu-latest rust: stable features: "" - os: ubuntu-latest rust: beta features: --all-features - os: ubuntu-latest rust: stable features: --all-features target: --target armv5te-unknown-linux-gnueabi use-cross: true runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} - run: cargo test ${{ matrix.features }} cross: runs-on: ubuntu-latest strategy: matrix: target: [armv5te-unknown-linux-gnueabi] steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: target: ${{ matrix.target }} - uses: taiki-e/install-action@cross - run: cross build --target ${{ matrix.target }} --all-features msrv: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: toolchain: "1.63" - run: cargo check --lib --all-features lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: toolchain: stable components: rustfmt, clippy - run: cargo fmt --all -- --check - run: cargo clippy --all-targets --all-features -- -D warnings - name: doc run: cargo doc --no-deps --document-private-items env: RUSTDOCFLAGS: -Dwarnings audit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: EmbarkStudios/cargo-deny-action@v1 indicatif-0.17.7/.gitignore000064400000000000000000000000341046102023000136370ustar 00000000000000target Cargo.lock .DS_Store indicatif-0.17.7/Cargo.lock0000644000000560340000000000100110450ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "addr2line" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" dependencies = [ "gimli", ] [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "anstream" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is-terminal", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" [[package]] name = "anstyle-parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ "windows-sys 0.48.0", ] [[package]] name = "anstyle-wincon" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" dependencies = [ "anstyle", "windows-sys 0.48.0", ] [[package]] name = "arrayvec" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" dependencies = [ "addr2line", "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", ] [[package]] name = "bitflags" version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" [[package]] name = "bytes" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cc" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" version = "4.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fd304a20bff958a57f04c4e96a2e7594cc4490a0e809cbd48bb6437edaa452d" dependencies = [ "clap_builder", "clap_derive", "once_cell", ] [[package]] name = "clap_builder" version = "4.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01c6a3f08f1fe5662a35cfe393aec09c4df95f60ee93b7556505260f75eee9e1" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", ] [[package]] name = "clap_derive" version = "4.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" dependencies = [ "heck", "proc-macro2", "quote", "syn", ] [[package]] name = "clap_lex" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" [[package]] name = "colorchoice" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "console" version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" dependencies = [ "encode_unicode", "lazy_static", "libc", "unicode-width", "windows-sys 0.45.0", ] [[package]] name = "crossbeam-channel" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" dependencies = [ "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-deque" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" dependencies = [ "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", "memoffset", "scopeguard", ] [[package]] name = "crossbeam-utils" version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if", ] [[package]] name = "diff" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] name = "either" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "encode_unicode" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "errno" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" dependencies = [ "errno-dragonfly", "libc", "windows-sys 0.48.0", ] [[package]] name = "errno-dragonfly" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" dependencies = [ "cc", "libc", ] [[package]] name = "futures" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-executor" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-macro" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "futures-sink" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "getrandom" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "gimli" version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" [[package]] name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "indicatif" version = "0.17.7" dependencies = [ "clap", "console", "futures", "futures-core", "instant", "number_prefix", "once_cell", "portable-atomic", "pretty_assertions", "rand", "rayon", "tokio", "unicode-segmentation", "unicode-width", "vt100", ] [[package]] name = "instant" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", ] [[package]] name = "is-terminal" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", "rustix", "windows-sys 0.48.0", ] [[package]] name = "itoa" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "linux-raw-sys" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" [[package]] name = "log" version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg", ] [[package]] name = "miniz_oxide" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", ] [[package]] name = "num_cpus" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ "hermit-abi", "libc", ] [[package]] name = "number_prefix" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "object" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "pin-project-lite" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "portable-atomic" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f32154ba0af3a075eefa1eda8bb414ee928f62303a54ea85b8d6638ff1a6ee9e" [[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "pretty_assertions" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" dependencies = [ "diff", "yansi", ] [[package]] name = "proc-macro2" version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "rayon" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" dependencies = [ "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" dependencies = [ "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", "num_cpus", ] [[package]] name = "rustc-demangle" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.48.0", ] [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "slab" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ "autocfg", ] [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" version = "2.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tokio" version = "1.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" dependencies = [ "autocfg", "backtrace", "bytes", "pin-project-lite", ] [[package]] name = "unicode-ident" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "unicode-segmentation" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-width" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "utf8parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "vt100" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84cd863bf0db7e392ba3bd04994be3473491b31e66340672af5d11943c6274de" dependencies = [ "itoa", "log", "unicode-width", "vte", ] [[package]] name = "vte" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197" dependencies = [ "arrayvec", "utf8parse", "vte_generate_state_changes", ] [[package]] name = "vte_generate_state_changes" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff" dependencies = [ "proc-macro2", "quote", ] [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "windows-sys" version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ "windows-targets 0.42.2", ] [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.1", ] [[package]] name = "windows-targets" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ "windows_aarch64_gnullvm 0.42.2", "windows_aarch64_msvc 0.42.2", "windows_i686_gnu 0.42.2", "windows_i686_msvc 0.42.2", "windows_x86_64_gnu 0.42.2", "windows_x86_64_gnullvm 0.42.2", "windows_x86_64_msvc 0.42.2", ] [[package]] name = "windows-targets" version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" dependencies = [ "windows_aarch64_gnullvm 0.48.0", "windows_aarch64_msvc 0.48.0", "windows_i686_gnu 0.48.0", "windows_i686_msvc 0.48.0", "windows_x86_64_gnu 0.48.0", "windows_x86_64_gnullvm 0.48.0", "windows_x86_64_msvc 0.48.0", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_i686_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "yansi" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" indicatif-0.17.7/Cargo.toml0000644000000043200000000000100110570ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.63" name = "indicatif" version = "0.17.7" exclude = ["screenshots/*"] description = "A progress bar and cli reporting library for Rust" documentation = "https://docs.rs/indicatif" readme = "README.md" keywords = [ "cli", "progress", "pb", "colors", "progressbar", ] categories = ["command-line-interface"] license = "MIT" repository = "https://github.com/console-rs/indicatif" [package.metadata.docs.rs] all-features = true rustdoc-args = [ "--cfg", "docsrs", ] [dependencies.console] version = "0.15" features = ["ansi-parsing"] default-features = false [dependencies.futures-core] version = "0.3" optional = true default-features = false [dependencies.number_prefix] version = "0.4" [dependencies.portable-atomic] version = "1.0.0" [dependencies.rayon] version = "1.1" optional = true [dependencies.tokio] version = "1" features = ["io-util"] optional = true [dependencies.unicode-segmentation] version = "1" optional = true [dependencies.unicode-width] version = "0.1" optional = true [dependencies.vt100] version = "0.15.1" optional = true [dev-dependencies.clap] version = "4" features = [ "color", "derive", ] [dev-dependencies.futures] version = "0.3" [dev-dependencies.once_cell] version = "1" [dev-dependencies.pretty_assertions] version = "1.4.0" [dev-dependencies.rand] version = "0.8" [dev-dependencies.tokio] version = "1" features = [ "fs", "time", "rt", ] [features] default = [ "unicode-width", "console/unicode-width", ] futures = ["dep:futures-core"] improved_unicode = [ "unicode-segmentation", "unicode-width", "console/unicode-width", ] in_memory = ["vt100"] [target."cfg(target_arch = \"wasm32\")".dependencies.instant] version = "0.1" indicatif-0.17.7/Cargo.toml.orig000064400000000000000000000027641046102023000145520ustar 00000000000000[package] name = "indicatif" version = "0.17.7" edition = "2021" rust-version = "1.63" description = "A progress bar and cli reporting library for Rust" keywords = ["cli", "progress", "pb", "colors", "progressbar"] categories = ["command-line-interface"] license = "MIT" repository = "https://github.com/console-rs/indicatif" documentation = "https://docs.rs/indicatif" readme = "README.md" exclude = ["screenshots/*"] [dependencies] console = { version = "0.15", default-features = false, features = ["ansi-parsing"] } futures-core = { version = "0.3", default-features = false, optional = true } number_prefix = "0.4" portable-atomic = "1.0.0" rayon = { version = "1.1", optional = true } tokio = { version = "1", optional = true, features = ["io-util"] } unicode-segmentation = { version = "1", optional = true } unicode-width = { version = "0.1", optional = true } vt100 = { version = "0.15.1", optional = true } [dev-dependencies] clap = { version = "4", features = ["color", "derive"] } once_cell = "1" rand = "0.8" tokio = { version = "1", features = ["fs", "time", "rt"] } futures = "0.3" # so the doctest for wrap_stream is nice pretty_assertions = "1.4.0" [target.'cfg(target_arch = "wasm32")'.dependencies] instant = "0.1" [features] default = ["unicode-width", "console/unicode-width"] improved_unicode = ["unicode-segmentation", "unicode-width", "console/unicode-width"] in_memory = ["vt100"] futures = ["dep:futures-core"] [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] indicatif-0.17.7/LICENSE000064400000000000000000000021301046102023000126530ustar 00000000000000The MIT License (MIT) Copyright (c) 2017 Armin Ronacher Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. indicatif-0.17.7/README.md000064400000000000000000000026561046102023000131420ustar 00000000000000# indicatif [![Documentation](https://docs.rs/indicatif/badge.svg)](https://docs.rs/indicatif/) [![Crates.io](https://img.shields.io/crates/v/indicatif.svg)](https://crates.io/crates/indicatif) [![Build status](https://github.com/console-rs/indicatif/workflows/CI/badge.svg)](https://github.com/console-rs/indicatif/actions/workflows/rust.yml) [![Chat](https://img.shields.io/discord/976380008299917365?logo=discord)](https://discord.gg/YHmNA3De4W) A Rust library for indicating progress in command line applications to users. This currently primarily provides progress bars and spinners as well as basic color support, but there are bigger plans for the future of this! ## Examples [examples/yarnish.rs](examples/yarnish.rs) [examples/download.rs](examples/download.rs) [examples/multi.rs](examples/multi.rs) [examples/single.rs](examples/single.rs) ## Integrations You can use [indicatif-log-bridge](https://crates.io/crates/indicatif-log-bridge) to integrate with the [log crate](https://crates.io/crates/log) and avoid having both fight for your terminal. indicatif-0.17.7/deny.toml000064400000000000000000000000731046102023000135060ustar 00000000000000[licenses] allow-osi-fsf-free = "either" copyleft = "deny" indicatif-0.17.7/examples/cargo.rs000064400000000000000000000072731046102023000151420ustar 00000000000000use std::sync::{mpsc, Arc, Mutex}; use std::thread; use std::time::{Duration, Instant}; use console::{Style, Term}; use indicatif::{HumanDuration, ProgressBar, ProgressStyle}; use rand::Rng; static CRATES: &[(&str, &str)] = &[ ("console", "v0.14.1"), ("lazy_static", "v1.4.0"), ("libc", "v0.2.93"), ("regex", "v1.4.6"), ("regex-syntax", "v0.6.23"), ("terminal_size", "v0.1.16"), ("libc", "v0.2.93"), ("unicode-width", "v0.1.8"), ("lazy_static", "v1.4.0"), ("number_prefix", "v0.4.0"), ("regex", "v1.4.6"), ("rand", "v0.8.3"), ("getrandom", "v0.2.2"), ("cfg-if", "v1.0.0"), ("libc", "v0.2.93"), ("rand_chacha", "v0.3.0"), ("ppv-lite86", "v0.2.10"), ("rand_core", "v0.6.2"), ("getrandom", "v0.2.2"), ("rand_core", "v0.6.2"), ("tokio", "v1.5.0"), ("bytes", "v1.0.1"), ("pin-project-lite", "v0.2.6"), ("slab", "v0.4.3"), ("indicatif", "v0.15.0"), ]; fn main() { // number of cpus const NUM_CPUS: usize = 4; let start = Instant::now(); // mimic cargo progress bar although it behaves a bit different let pb = ProgressBar::new(CRATES.len() as u64); pb.set_style( ProgressStyle::with_template( // note that bar size is fixed unlike cargo which is dynamic // and also the truncation in cargo uses trailers (`...`) if Term::stdout().size().1 > 80 { "{prefix:>12.cyan.bold} [{bar:57}] {pos}/{len} {wide_msg}" } else { "{prefix:>12.cyan.bold} [{bar:57}] {pos}/{len}" }, ) .unwrap() .progress_chars("=> "), ); pb.set_prefix("Building"); // process in another thread // crates to be iterated but not exactly a tree let crates = Arc::new(Mutex::new(CRATES.iter())); let (tx, rx) = mpsc::channel(); for n in 0..NUM_CPUS { let tx = tx.clone(); let crates = crates.clone(); thread::spawn(move || { let mut rng = rand::thread_rng(); loop { let krate = crates.lock().unwrap().next(); // notify main thread if n thread is processing a crate tx.send((n, krate)).unwrap(); if let Some(krate) = krate { thread::sleep(Duration::from_millis( // last compile and linking is always slow, let's mimic that if CRATES.last() == Some(krate) { rng.gen_range(1_000..2_000) } else { rng.gen_range(250..1_000) }, )); } else { break; } } }); } // drop tx to stop waiting drop(tx); let green_bold = Style::new().green().bold(); // do progress drawing in main thread let mut processing = [None; NUM_CPUS]; while let Ok((n, krate)) = rx.recv() { processing[n] = krate; let crates: Vec<&str> = processing .iter() .filter_map(|t| t.copied().map(|(name, _)| name)) .collect(); pb.set_message(crates.join(", ")); if let Some((name, version)) = krate { // crate is being built let line = format!( "{:>12} {} {}", green_bold.apply_to("Compiling"), name, version ); pb.println(line); pb.inc(1); } } pb.finish_and_clear(); // compilation is finished println!( "{:>12} dev [unoptimized + debuginfo] target(s) in {}", green_bold.apply_to("Finished"), HumanDuration(start.elapsed()) ); } indicatif-0.17.7/examples/cargowrap.rs000064400000000000000000000020621046102023000160230ustar 00000000000000use std::io::{BufRead, BufReader}; use std::process; use std::time::{Duration, Instant}; use indicatif::{HumanDuration, ProgressBar, ProgressStyle}; pub fn main() { let started = Instant::now(); println!("Compiling package in release mode..."); let pb = ProgressBar::new_spinner(); pb.enable_steady_tick(Duration::from_millis(200)); pb.set_style( ProgressStyle::with_template("{spinner:.dim.bold} cargo: {wide_msg}") .unwrap() .tick_chars("/|\\- "), ); let mut p = process::Command::new("cargo") .arg("build") .arg("--release") .stderr(process::Stdio::piped()) .spawn() .unwrap(); for line in BufReader::new(p.stderr.take().unwrap()).lines() { let line = line.unwrap(); let stripped_line = line.trim(); if !stripped_line.is_empty() { pb.set_message(stripped_line.to_owned()); } pb.tick(); } p.wait().unwrap(); pb.finish_and_clear(); println!("Done in {}", HumanDuration(started.elapsed())); } indicatif-0.17.7/examples/download-continued.rs000064400000000000000000000013271046102023000176360ustar 00000000000000use std::cmp::min; use std::thread; use std::time::Duration; use indicatif::{ProgressBar, ProgressStyle}; fn main() { let mut downloaded = 69369369; let total_size = 231231231; let pb = ProgressBar::new(total_size); pb.set_style( ProgressStyle::with_template( "{spinner:.green} [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({eta})", ) .unwrap() .progress_chars("#>-"), ); pb.set_position(downloaded); pb.reset_eta(); while downloaded < total_size { downloaded = min(downloaded + 123211, total_size); pb.set_position(downloaded); thread::sleep(Duration::from_millis(12)); } pb.finish_with_message("downloaded"); } indicatif-0.17.7/examples/download-speed.rs000064400000000000000000000012521046102023000167430ustar 00000000000000use std::cmp::min; use std::thread; use std::time::Duration; use indicatif::{ProgressBar, ProgressStyle}; fn main() { let mut downloaded = 0; let total_size = 231231231; let pb = ProgressBar::new(total_size); pb.set_style(ProgressStyle::with_template("{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})") .unwrap() .progress_chars("#>-")); while downloaded < total_size { let new = min(downloaded + 223211, total_size); downloaded = new; pb.set_position(new); thread::sleep(Duration::from_millis(12)); } pb.finish_with_message("downloaded"); } indicatif-0.17.7/examples/download.rs000064400000000000000000000014631046102023000156510ustar 00000000000000use std::thread; use std::time::Duration; use std::{cmp::min, fmt::Write}; use indicatif::{ProgressBar, ProgressState, ProgressStyle}; fn main() { let mut downloaded = 0; let total_size = 231231231; let pb = ProgressBar::new(total_size); pb.set_style(ProgressStyle::with_template("{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({eta})") .unwrap() .with_key("eta", |state: &ProgressState, w: &mut dyn Write| write!(w, "{:.1}s", state.eta().as_secs_f64()).unwrap()) .progress_chars("#>-")); while downloaded < total_size { let new = min(downloaded + 223211, total_size); downloaded = new; pb.set_position(new); thread::sleep(Duration::from_millis(12)); } pb.finish_with_message("downloaded"); } indicatif-0.17.7/examples/fastbar.rs000064400000000000000000000011071046102023000154570ustar 00000000000000use indicatif::ProgressBar; fn many_units_of_easy_work(n: u64, label: &str) { let pb = ProgressBar::new(n); let mut sum = 0; for i in 0..n { // Any quick computation, followed by an update to the progress bar. sum += 2 * i + 3; pb.inc(1); } pb.finish(); println!("[{}] Sum ({}) calculated in {:?}", label, sum, pb.elapsed()); } fn main() { const N: u64 = 1 << 20; // Perform a long sequence of many simple computations monitored by a // default progress bar. many_units_of_easy_work(N, "Default progress bar "); } indicatif-0.17.7/examples/finebars.rs000064400000000000000000000024301046102023000156260ustar 00000000000000use std::thread; use std::time::Duration; use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; use rand::{thread_rng, Rng}; fn main() { let styles = [ ("Rough bar:", "█ ", "red"), ("Fine bar: ", "█▉▊▋▌▍▎▏ ", "yellow"), ("Vertical: ", "█▇▆▅▄▃▂▁ ", "green"), ("Fade in: ", "█▓▒░ ", "blue"), ("Blocky: ", "█▛▌▖ ", "magenta"), ]; let m = MultiProgress::new(); let handles: Vec<_> = styles .iter() .map(|s| { let pb = m.add(ProgressBar::new(512)); pb.set_style( ProgressStyle::with_template(&format!("{{prefix:.bold}}▕{{bar:.{}}}▏{{msg}}", s.2)) .unwrap() .progress_chars(s.1), ); pb.set_prefix(s.0); let wait = Duration::from_millis(thread_rng().gen_range(10..30)); thread::spawn(move || { for i in 0..512 { thread::sleep(wait); pb.inc(1); pb.set_message(format!("{:3}%", 100 * i / 512)); } pb.finish_with_message("100%"); }) }) .collect(); for h in handles { let _ = h.join(); } } indicatif-0.17.7/examples/iterator.rs000064400000000000000000000015251046102023000156720ustar 00000000000000use std::thread; use std::time::Duration; use indicatif::{ProgressBar, ProgressIterator, ProgressStyle}; fn main() { // Default styling, attempt to use Iterator::size_hint to count input size for _ in (0..1000).progress() { // ... thread::sleep(Duration::from_millis(5)); } // Provide explicit number of elements in iterator for _ in (0..1000).progress_count(1000) { // ... thread::sleep(Duration::from_millis(5)); } // Provide a custom bar style let pb = ProgressBar::new(1000); pb.set_style( ProgressStyle::with_template( "{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] ({pos}/{len}, ETA {eta})", ) .unwrap(), ); for _ in (0..1000).progress_with(pb) { // ... thread::sleep(Duration::from_millis(5)); } } indicatif-0.17.7/examples/log.rs000064400000000000000000000004611046102023000146200ustar 00000000000000use std::thread; use std::time::Duration; use indicatif::ProgressBar; fn main() { let pb = ProgressBar::new(100); for i in 0..100 { thread::sleep(Duration::from_millis(25)); pb.println(format!("[+] finished #{i}")); pb.inc(1); } pb.finish_with_message("done"); } indicatif-0.17.7/examples/long-spinner.rs000064400000000000000000000015461046102023000164570ustar 00000000000000use std::thread; use std::time::Duration; use indicatif::{ProgressBar, ProgressStyle}; fn main() { let pb = ProgressBar::new_spinner(); pb.enable_steady_tick(Duration::from_millis(120)); pb.set_style( ProgressStyle::with_template("{spinner:.blue} {msg}") .unwrap() // For more spinners check out the cli-spinners project: // https://github.com/sindresorhus/cli-spinners/blob/master/spinners.json .tick_strings(&[ "▹▹▹▹▹", "▸▹▹▹▹", "▹▸▹▹▹", "▹▹▸▹▹", "▹▹▹▸▹", "▹▹▹▹▸", "▪▪▪▪▪", ]), ); pb.set_message("Calculating..."); thread::sleep(Duration::from_secs(5)); pb.finish_with_message("Done"); } indicatif-0.17.7/examples/message.rs000064400000000000000000000003701046102023000154620ustar 00000000000000use std::{thread, time::Duration}; use indicatif::ProgressBar; fn main() { let pb = ProgressBar::new(100).with_message("Frobbing the widget"); for _ in 0..100 { thread::sleep(Duration::from_millis(30)); pb.inc(1); } } indicatif-0.17.7/examples/morebars.rs000064400000000000000000000013441046102023000156520ustar 00000000000000use std::sync::Arc; use std::thread; use std::time::Duration; use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; fn main() { let m = Arc::new(MultiProgress::new()); let sty = ProgressStyle::with_template("{bar:40.green/yellow} {pos:>7}/{len:7}").unwrap(); let pb = m.add(ProgressBar::new(5)); pb.set_style(sty.clone()); // make sure we show up at all. otherwise no rendering // event. pb.tick(); for _ in 0..5 { let pb2 = m.add(ProgressBar::new(128)); pb2.set_style(sty.clone()); for _ in 0..128 { thread::sleep(Duration::from_millis(5)); pb2.inc(1); } pb2.finish(); pb.inc(1); } pb.finish_with_message("done"); } indicatif-0.17.7/examples/multi-tree-ext.rs000064400000000000000000000210131046102023000167200ustar 00000000000000use clap::Parser; use std::fmt::Debug; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use std::thread; use std::time::Duration; use console::style; use indicatif::{MultiProgress, MultiProgressAlignment, ProgressBar, ProgressStyle}; use once_cell::sync::Lazy; use rand::rngs::ThreadRng; use rand::{Rng, RngCore}; #[derive(Debug, Clone)] enum Action { ModifyTree(usize), IncProgressBar(usize), Stop, } #[derive(Clone, Debug)] enum Elem { AddItem(Item), RemoveItem(Index), } #[derive(Clone, Debug)] struct Item { key: String, index: usize, indent: usize, progress_bar: ProgressBar, } #[derive(Clone, Debug)] struct Index(usize); const PB_LEN: u64 = 32; static ELEM_IDX: AtomicUsize = AtomicUsize::new(0); static ELEMENTS: Lazy<[Elem; 27]> = Lazy::new(|| { [ Elem::AddItem(Item { indent: 9, index: 0, progress_bar: ProgressBar::new(PB_LEN), key: "dog".to_string(), }), Elem::AddItem(Item { indent: 0, index: 0, progress_bar: ProgressBar::new(PB_LEN), key: "temp_1".to_string(), }), Elem::AddItem(Item { indent: 8, index: 1, progress_bar: ProgressBar::new(PB_LEN), key: "lazy".to_string(), }), Elem::AddItem(Item { indent: 0, index: 1, progress_bar: ProgressBar::new(PB_LEN), key: "temp_2".to_string(), }), Elem::AddItem(Item { indent: 1, index: 0, progress_bar: ProgressBar::new(PB_LEN), key: "the".to_string(), }), Elem::AddItem(Item { indent: 0, index: 0, progress_bar: ProgressBar::new(PB_LEN), key: "temp_3".to_string(), }), Elem::AddItem(Item { indent: 7, index: 3, progress_bar: ProgressBar::new(PB_LEN), key: "a".to_string(), }), Elem::AddItem(Item { indent: 0, index: 3, progress_bar: ProgressBar::new(PB_LEN), key: "temp_4".to_string(), }), Elem::AddItem(Item { indent: 6, index: 2, progress_bar: ProgressBar::new(PB_LEN), key: "over".to_string(), }), Elem::RemoveItem(Index(6)), Elem::RemoveItem(Index(4)), Elem::RemoveItem(Index(3)), Elem::RemoveItem(Index(0)), Elem::AddItem(Item { indent: 0, index: 2, progress_bar: ProgressBar::new(PB_LEN), key: "temp_5".to_string(), }), Elem::AddItem(Item { indent: 4, index: 1, progress_bar: ProgressBar::new(PB_LEN), key: "fox".to_string(), }), Elem::AddItem(Item { indent: 0, index: 1, progress_bar: ProgressBar::new(PB_LEN), key: "temp_6".to_string(), }), Elem::AddItem(Item { indent: 2, index: 1, progress_bar: ProgressBar::new(PB_LEN), key: "quick".to_string(), }), Elem::AddItem(Item { indent: 0, index: 1, progress_bar: ProgressBar::new(PB_LEN), key: "temp_7".to_string(), }), Elem::AddItem(Item { indent: 5, index: 5, progress_bar: ProgressBar::new(PB_LEN), key: "jumps".to_string(), }), Elem::AddItem(Item { indent: 0, index: 5, progress_bar: ProgressBar::new(PB_LEN), key: "temp_8".to_string(), }), Elem::AddItem(Item { indent: 3, index: 4, progress_bar: ProgressBar::new(PB_LEN), key: "brown".to_string(), }), Elem::AddItem(Item { indent: 0, index: 3, progress_bar: ProgressBar::new(PB_LEN), key: "temp_9".to_string(), }), Elem::RemoveItem(Index(10)), Elem::RemoveItem(Index(7)), Elem::RemoveItem(Index(4)), Elem::RemoveItem(Index(3)), Elem::RemoveItem(Index(1)), ] }); #[derive(Debug, Parser)] pub struct Config { #[clap(long)] bottom_alignment: bool, } /// The example demonstrates the usage of `MultiProgress` and further extends `multi-tree` example. /// Now the example has 3 different actions implemented, and the item tree can be modified /// by inserting or removing progress bars. The progress bars to be removed eventually /// have messages with pattern `"temp_*"`. /// /// Also the command option `--bottom-alignment` is used to control the vertical alignment of the /// `MultiProgress`. To enable this run it with /// ```ignore /// cargo run --example multi-tree-ext -- --bottom-alignment /// ``` pub fn main() { let conf: Config = Config::parse(); let mp = Arc::new(MultiProgress::new()); let alignment = if conf.bottom_alignment { MultiProgressAlignment::Bottom } else { MultiProgressAlignment::Top }; mp.set_alignment(alignment); let sty_main = ProgressStyle::with_template("{bar:40.green/yellow} {pos:>4}/{len:4}").unwrap(); let sty_aux = ProgressStyle::with_template("[{pos:>2}/{len:2}] {prefix}{spinner:.green} {msg}").unwrap(); let sty_fin = ProgressStyle::with_template("[{pos:>2}/{len:2}] {prefix}{msg}").unwrap(); let pb_main = mp.add(ProgressBar::new( ELEMENTS .iter() .map(|e| match e { Elem::AddItem(item) => item.progress_bar.length().unwrap(), Elem::RemoveItem(_) => 1, }) .sum(), )); pb_main.set_style(sty_main); for e in ELEMENTS.iter() { match e { Elem::AddItem(item) => item.progress_bar.set_style(sty_aux.clone()), Elem::RemoveItem(_) => {} } } let mut items: Vec<&Item> = Vec::with_capacity(ELEMENTS.len()); let mp2 = Arc::clone(&mp); let mut rng = ThreadRng::default(); pb_main.tick(); loop { match get_action(&mut rng, &items) { Action::Stop => { // all elements were exhausted pb_main.finish(); return; } Action::ModifyTree(elem_idx) => match &ELEMENTS[elem_idx] { Elem::AddItem(item) => { let pb = mp2.insert(item.index, item.progress_bar.clone()); pb.set_prefix(" ".repeat(item.indent)); pb.set_message(&item.key); items.insert(item.index, item); } Elem::RemoveItem(Index(index)) => { let item = items.remove(*index); let pb = &item.progress_bar; mp2.remove(pb); pb_main.inc(pb.length().unwrap() - pb.position()); } }, Action::IncProgressBar(item_idx) => { let item = &items[item_idx]; item.progress_bar.inc(1); let pos = item.progress_bar.position(); if pos >= item.progress_bar.length().unwrap() { item.progress_bar.set_style(sty_fin.clone()); item.progress_bar.finish_with_message(format!( "{} {}", style("✔").green(), item.key )); } pb_main.inc(1); } } thread::sleep(Duration::from_millis(20)); } } /// The function guarantees to return the action, that is valid for the current tree. fn get_action(rng: &mut dyn RngCore, items: &[&Item]) -> Action { let elem_idx = ELEM_IDX.load(Ordering::SeqCst); // the indices of those items, that not completed yet let uncompleted = items .iter() .enumerate() .filter(|(_, item)| { let pos = item.progress_bar.position(); pos < item.progress_bar.length().unwrap() }) .map(|(idx, _)| idx) .collect::>(); let k = rng.gen_range(0..16); if (k > 0 || k == 0 && elem_idx == ELEMENTS.len()) && !uncompleted.is_empty() { let idx = rng.gen_range(0..uncompleted.len() as u64) as usize; Action::IncProgressBar(uncompleted[idx]) } else if elem_idx < ELEMENTS.len() { ELEM_IDX.fetch_add(1, Ordering::SeqCst); Action::ModifyTree(elem_idx) } else { // nothing to do more Action::Stop } } indicatif-0.17.7/examples/multi-tree.rs000064400000000000000000000135441046102023000161340ustar 00000000000000use std::fmt::Debug; use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; use once_cell::sync::Lazy; use rand::rngs::ThreadRng; use rand::{Rng, RngCore}; #[derive(Debug, Clone)] enum Action { AddProgressBar(usize), IncProgressBar(usize), } #[derive(Clone, Debug)] struct Elem { key: String, index: usize, indent: usize, progress_bar: ProgressBar, } static ELEMENTS: Lazy<[Elem; 9]> = Lazy::new(|| { [ Elem { indent: 1, index: 0, progress_bar: ProgressBar::new(32), key: "jumps".to_string(), }, Elem { indent: 2, index: 1, progress_bar: ProgressBar::new(32), key: "lazy".to_string(), }, Elem { indent: 0, index: 0, progress_bar: ProgressBar::new(32), key: "the".to_string(), }, Elem { indent: 3, index: 3, progress_bar: ProgressBar::new(32), key: "dog".to_string(), }, Elem { indent: 2, index: 2, progress_bar: ProgressBar::new(32), key: "over".to_string(), }, Elem { indent: 2, index: 1, progress_bar: ProgressBar::new(32), key: "brown".to_string(), }, Elem { indent: 1, index: 1, progress_bar: ProgressBar::new(32), key: "quick".to_string(), }, Elem { indent: 3, index: 5, progress_bar: ProgressBar::new(32), key: "a".to_string(), }, Elem { indent: 3, index: 3, progress_bar: ProgressBar::new(32), key: "fox".to_string(), }, ] }); /// The example implements the tree-like collection of progress bars, where elements are /// added on the fly and progress bars get incremented until all elements is added and /// all progress bars finished. /// On each iteration `get_action` function returns some action, and when the tree gets /// complete, the function returns `None`, which finishes the loop. fn main() { let mp = Arc::new(MultiProgress::new()); let sty_main = ProgressStyle::with_template("{bar:40.green/yellow} {pos:>4}/{len:4}").unwrap(); let sty_aux = ProgressStyle::with_template("{spinner:.green} {msg} {pos:>4}/{len:4}").unwrap(); let pb_main = mp.add(ProgressBar::new( ELEMENTS .iter() .map(|e| e.progress_bar.length().unwrap()) .sum(), )); pb_main.set_style(sty_main); for elem in ELEMENTS.iter() { elem.progress_bar.set_style(sty_aux.clone()); } let tree: Arc>> = Arc::new(Mutex::new(Vec::with_capacity(ELEMENTS.len()))); let tree2 = Arc::clone(&tree); let mp2 = Arc::clone(&mp); let _ = thread::spawn(move || { let mut rng = ThreadRng::default(); pb_main.tick(); loop { thread::sleep(Duration::from_millis(15)); match get_action(&mut rng, &tree) { None => { // all elements were exhausted pb_main.finish(); return; } Some(Action::AddProgressBar(el_idx)) => { let elem = &ELEMENTS[el_idx]; let pb = mp2.insert(elem.index + 1, elem.progress_bar.clone()); pb.set_message(format!("{} {}", " ".repeat(elem.indent), elem.key)); tree.lock().unwrap().insert(elem.index, elem); } Some(Action::IncProgressBar(el_idx)) => { let elem = &tree.lock().unwrap()[el_idx]; elem.progress_bar.inc(1); let pos = elem.progress_bar.position(); if pos >= elem.progress_bar.length().unwrap() { elem.progress_bar.finish_with_message(format!( "{}{} {}", " ".repeat(elem.indent), "✔", elem.key )); } pb_main.inc(1); } } } }) .join(); println!("==============================="); println!("the tree should be the same as:"); for elem in tree2.lock().unwrap().iter() { println!("{} {}", " ".repeat(elem.indent), elem.key); } } /// The function guarantees to return the action, that is valid for the current tree. fn get_action(rng: &mut dyn RngCore, tree: &Mutex>) -> Option { let elem_len = ELEMENTS.len() as u64; let list_len = tree.lock().unwrap().len() as u64; let sum_free = tree .lock() .unwrap() .iter() .map(|e| { let pos = e.progress_bar.position(); let len = e.progress_bar.length().unwrap(); len - pos }) .sum::(); if sum_free == 0 && list_len == elem_len { // nothing to do more None } else if sum_free == 0 && list_len < elem_len { // there is no place to make an increment Some(Action::AddProgressBar(tree.lock().unwrap().len())) } else { loop { let list = tree.lock().unwrap(); let k = rng.gen_range(0..17); if k == 0 && list_len < elem_len { return Some(Action::AddProgressBar(list.len())); } else { let l = (k % list_len) as usize; let pos = list[l].progress_bar.position(); let len = list[l].progress_bar.length(); if pos < len.unwrap() { return Some(Action::IncProgressBar(l)); } } } } } indicatif-0.17.7/examples/multi.rs000064400000000000000000000035161046102023000151750ustar 00000000000000use std::thread; use std::time::Duration; use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; use rand::Rng; fn main() { let m = MultiProgress::new(); let sty = ProgressStyle::with_template( "[{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {msg}", ) .unwrap() .progress_chars("##-"); let n = 200; let pb = m.add(ProgressBar::new(n)); pb.set_style(sty.clone()); pb.set_message("todo"); let pb2 = m.add(ProgressBar::new(n)); pb2.set_style(sty.clone()); pb2.set_message("finished"); let pb3 = m.insert_after(&pb2, ProgressBar::new(1024)); pb3.set_style(sty); m.println("starting!").unwrap(); let mut threads = vec![]; let m_clone = m.clone(); let h3 = thread::spawn(move || { for i in 0..1024 { thread::sleep(Duration::from_millis(2)); pb3.set_message(format!("item #{}", i + 1)); pb3.inc(1); } m_clone.println("pb3 is done!").unwrap(); pb3.finish_with_message("done"); }); for i in 0..n { thread::sleep(Duration::from_millis(15)); if i == n / 3 { thread::sleep(Duration::from_secs(2)); } pb.inc(1); let m = m.clone(); let pb2 = pb2.clone(); threads.push(thread::spawn(move || { let spinner = m.add(ProgressBar::new_spinner().with_message(i.to_string())); spinner.enable_steady_tick(Duration::from_millis(100)); thread::sleep( rand::thread_rng().gen_range(Duration::from_secs(1)..Duration::from_secs(5)), ); pb2.inc(1); })); } pb.finish_with_message("all jobs started"); for thread in threads { let _ = thread.join(); } let _ = h3.join(); pb2.finish_with_message("all jobs done"); m.clear().unwrap(); } indicatif-0.17.7/examples/single.rs000064400000000000000000000004001046102023000153110ustar 00000000000000use std::thread; use std::time::Duration; use indicatif::ProgressBar; fn main() { let pb = ProgressBar::new(1024); for _ in 0..1024 { thread::sleep(Duration::from_millis(5)); pb.inc(1); } pb.finish_with_message("done"); } indicatif-0.17.7/examples/slow.rs000064400000000000000000000005151046102023000150230ustar 00000000000000use indicatif::{ProgressBar, ProgressStyle}; use std::time::Duration; fn main() { let progress = ProgressBar::new(10).with_style(ProgressStyle::default_bar().progress_chars("🔐🔑🕓")); for _ in 0..10 { progress.inc(1); std::thread::sleep(Duration::from_secs(1)); } progress.finish(); } indicatif-0.17.7/examples/spinner-loop.rs000064400000000000000000000010601046102023000164600ustar 00000000000000use std::thread; use std::time::Duration; use indicatif::{ProgressBar, ProgressFinish}; fn main() { let mut spinner: Option = None; for i in 0..3 { let new_spinner = ProgressBar::new_spinner() .with_message(format!("doing stuff {}", i)) .with_finish(ProgressFinish::AndLeave); new_spinner.enable_steady_tick(Duration::from_millis(10)); thread::sleep(Duration::from_millis(500)); println!("\n\nreplace {}\n\n", i); spinner.replace(new_spinner).map(|t| t.finish()); } } indicatif-0.17.7/examples/steady.rs000064400000000000000000000032661046102023000153360ustar 00000000000000use std::{ thread::sleep, time::{Duration, Instant}, }; use indicatif::{ProgressBar, ProgressIterator, ProgressStyle}; fn main() { let iterations = 1000; // Set the array with all the blocksizes to test let blocksizes: [usize; 7] = [16, 64, 256, 1024, 4096, 16384, 65536]; // Set the array with all the durations to save let mut elapsed: [Duration; 7] = [Duration::ZERO; 7]; for (pos, blocksize) in blocksizes.iter().enumerate() { // Set up the style for the progressbar let sty = ProgressStyle::default_spinner() .tick_strings(&[ "▹▹▹▹▹", "▸▹▹▹▹", "▹▸▹▹▹", "▹▹▸▹▹", "▹▹▹▸▹", "▹▹▹▹▸", "▪▪▪▪▪", ]) .template("{prefix} {pos:>4}/{len:4} Iterations per second: {per_sec} {spinner} {msg}") .unwrap(); // Set up the progress bar and apply the style let pb = ProgressBar::new(iterations); pb.set_style(sty); pb.enable_steady_tick(Duration::from_millis(120)); pb.set_prefix(format!("Doing test with Blocksize {:5?}:", blocksize)); // Iterate for the given number of iterations // for _ in (0..iterations) { for _ in (0..iterations).progress_with(pb) { // pb.inc(1); // Take a timestamp for timemeasurement later on let now = Instant::now(); sleep(Duration::from_millis(1)); // Save the elapsed time for later evaluation elapsed[pos] += now.elapsed(); } // pb.finish_using_style(); } } indicatif-0.17.7/examples/tokio.rs000064400000000000000000000015421046102023000151650ustar 00000000000000use std::time::Duration; use indicatif::ProgressBar; use tokio::runtime; use tokio::time::interval; fn main() { // Plain progress bar, totaling 1024 steps. let steps = 1024; let pb = ProgressBar::new(steps); // Stream of events, triggering every 5ms. let rt = runtime::Builder::new_current_thread() .enable_time() .build() .expect("failed to create runtime"); // Future computation which runs for `steps` interval events, // incrementing one step of the progress bar each time. let future = async { let mut intv = interval(Duration::from_millis(5)); for _ in 0..steps { intv.tick().await; pb.inc(1); } }; // Drive the future to completion, blocking until done. rt.block_on(future); // Mark the progress bar as finished. pb.finish(); } indicatif-0.17.7/examples/yarnish.rs000064400000000000000000000052311046102023000155140ustar 00000000000000use std::thread; use std::time::{Duration, Instant}; use console::{style, Emoji}; use indicatif::{HumanDuration, MultiProgress, ProgressBar, ProgressStyle}; use rand::seq::SliceRandom; use rand::Rng; static PACKAGES: &[&str] = &[ "fs-events", "my-awesome-module", "emoji-speaker", "wrap-ansi", "stream-browserify", "acorn-dynamic-import", ]; static COMMANDS: &[&str] = &[ "cmake .", "make", "make clean", "gcc foo.c -o foo", "gcc bar.c -o bar", "./helper.sh rebuild-cache", "make all-clean", "make test", ]; static LOOKING_GLASS: Emoji<'_, '_> = Emoji("🔍 ", ""); static TRUCK: Emoji<'_, '_> = Emoji("🚚 ", ""); static CLIP: Emoji<'_, '_> = Emoji("🔗 ", ""); static PAPER: Emoji<'_, '_> = Emoji("📃 ", ""); static SPARKLE: Emoji<'_, '_> = Emoji("✨ ", ":-)"); pub fn main() { let mut rng = rand::thread_rng(); let started = Instant::now(); let spinner_style = ProgressStyle::with_template("{prefix:.bold.dim} {spinner} {wide_msg}") .unwrap() .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ "); println!( "{} {}Resolving packages...", style("[1/4]").bold().dim(), LOOKING_GLASS ); println!( "{} {}Fetching packages...", style("[2/4]").bold().dim(), TRUCK ); println!( "{} {}Linking dependencies...", style("[3/4]").bold().dim(), CLIP ); let deps = 1232; let pb = ProgressBar::new(deps); for _ in 0..deps { thread::sleep(Duration::from_millis(3)); pb.inc(1); } pb.finish_and_clear(); println!( "{} {}Building fresh packages...", style("[4/4]").bold().dim(), PAPER ); let m = MultiProgress::new(); let handles: Vec<_> = (0..4u32) .map(|i| { let count = rng.gen_range(30..80); let pb = m.add(ProgressBar::new(count)); pb.set_style(spinner_style.clone()); pb.set_prefix(format!("[{}/?]", i + 1)); thread::spawn(move || { let mut rng = rand::thread_rng(); let pkg = PACKAGES.choose(&mut rng).unwrap(); for _ in 0..count { let cmd = COMMANDS.choose(&mut rng).unwrap(); thread::sleep(Duration::from_millis(rng.gen_range(25..200))); pb.set_message(format!("{pkg}: {cmd}")); pb.inc(1); } pb.finish_with_message("waiting..."); }) }) .collect(); for h in handles { let _ = h.join(); } m.clear().unwrap(); println!("{} Done in {}", SPARKLE, HumanDuration(started.elapsed())); } indicatif-0.17.7/src/draw_target.rs000064400000000000000000000434211046102023000153160ustar 00000000000000use std::io; use std::sync::{Arc, RwLock, RwLockWriteGuard}; use std::thread::panicking; use std::time::Duration; #[cfg(not(target_arch = "wasm32"))] use std::time::Instant; use console::Term; #[cfg(target_arch = "wasm32")] use instant::Instant; use crate::multi::{MultiProgressAlignment, MultiState}; use crate::TermLike; /// Target for draw operations /// /// This tells a progress bar or a multi progress object where to paint to. /// The draw target is a stateful wrapper over a drawing destination and /// internally optimizes how often the state is painted to the output /// device. #[derive(Debug)] pub struct ProgressDrawTarget { kind: TargetKind, } impl ProgressDrawTarget { /// Draw to a buffered stdout terminal at a max of 20 times a second. /// /// For more information see [`ProgressDrawTarget::term`]. pub fn stdout() -> Self { Self::term(Term::buffered_stdout(), 20) } /// Draw to a buffered stderr terminal at a max of 20 times a second. /// /// This is the default draw target for progress bars. For more /// information see [`ProgressDrawTarget::term`]. pub fn stderr() -> Self { Self::term(Term::buffered_stderr(), 20) } /// Draw to a buffered stdout terminal at a max of `refresh_rate` times a second. /// /// For more information see [`ProgressDrawTarget::term`]. pub fn stdout_with_hz(refresh_rate: u8) -> Self { Self::term(Term::buffered_stdout(), refresh_rate) } /// Draw to a buffered stderr terminal at a max of `refresh_rate` times a second. /// /// For more information see [`ProgressDrawTarget::term`]. pub fn stderr_with_hz(refresh_rate: u8) -> Self { Self::term(Term::buffered_stderr(), refresh_rate) } pub(crate) fn new_remote(state: Arc>, idx: usize) -> Self { Self { kind: TargetKind::Multi { state, idx }, } } /// Draw to a terminal, with a specific refresh rate. /// /// Progress bars are by default drawn to terminals however if the /// terminal is not user attended the entire progress bar will be /// hidden. This is done so that piping to a file will not produce /// useless escape codes in that file. /// /// Will panic if refresh_rate is `0`. pub fn term(term: Term, refresh_rate: u8) -> Self { Self { kind: TargetKind::Term { term, last_line_count: 0, rate_limiter: RateLimiter::new(refresh_rate), draw_state: DrawState::default(), }, } } /// Draw to a boxed object that implements the [`TermLike`] trait. pub fn term_like(term_like: Box) -> Self { Self { kind: TargetKind::TermLike { inner: term_like, last_line_count: 0, rate_limiter: None, draw_state: DrawState::default(), }, } } /// Draw to a boxed object that implements the [`TermLike`] trait, /// with a specific refresh rate. pub fn term_like_with_hz(term_like: Box, refresh_rate: u8) -> Self { Self { kind: TargetKind::TermLike { inner: term_like, last_line_count: 0, rate_limiter: Option::from(RateLimiter::new(refresh_rate)), draw_state: DrawState::default(), }, } } /// A hidden draw target. /// /// This forces a progress bar to be not rendered at all. pub fn hidden() -> Self { Self { kind: TargetKind::Hidden, } } /// Returns true if the draw target is hidden. /// /// This is internally used in progress bars to figure out if overhead /// from drawing can be prevented. pub fn is_hidden(&self) -> bool { match self.kind { TargetKind::Hidden => true, TargetKind::Term { ref term, .. } => !term.is_term(), TargetKind::Multi { ref state, .. } => state.read().unwrap().is_hidden(), _ => false, } } /// Returns the current width of the draw target. pub(crate) fn width(&self) -> u16 { match self.kind { TargetKind::Term { ref term, .. } => term.size().1, TargetKind::Multi { ref state, .. } => state.read().unwrap().width(), TargetKind::Hidden => 0, TargetKind::TermLike { ref inner, .. } => inner.width(), } } /// Notifies the backing `MultiProgress` (if applicable) that the associated progress bar should /// be marked a zombie. pub(crate) fn mark_zombie(&self) { if let TargetKind::Multi { idx, state } = &self.kind { state.write().unwrap().mark_zombie(*idx); } } /// Apply the given draw state (draws it). pub(crate) fn drawable(&mut self, force_draw: bool, now: Instant) -> Option> { match &mut self.kind { TargetKind::Term { term, last_line_count, rate_limiter, draw_state, } => { if !term.is_term() { return None; } match force_draw || rate_limiter.allow(now) { true => Some(Drawable::Term { term, last_line_count, draw_state, }), false => None, // rate limited } } TargetKind::Multi { idx, state, .. } => { let state = state.write().unwrap(); Some(Drawable::Multi { idx: *idx, state, force_draw, now, }) } TargetKind::TermLike { inner, last_line_count, rate_limiter, draw_state, } => match force_draw || rate_limiter.as_mut().map_or(true, |r| r.allow(now)) { true => Some(Drawable::TermLike { term_like: &**inner, last_line_count, draw_state, }), false => None, // rate limited }, // Hidden, finished, or no need to refresh yet _ => None, } } /// Properly disconnects from the draw target pub(crate) fn disconnect(&self, now: Instant) { match self.kind { TargetKind::Term { .. } => {} TargetKind::Multi { idx, ref state, .. } => { let state = state.write().unwrap(); let _ = Drawable::Multi { state, idx, force_draw: true, now, } .clear(); } TargetKind::Hidden => {} TargetKind::TermLike { .. } => {} }; } pub(crate) fn remote(&self) -> Option<(&Arc>, usize)> { match &self.kind { TargetKind::Multi { state, idx } => Some((state, *idx)), _ => None, } } pub(crate) fn adjust_last_line_count(&mut self, adjust: LineAdjust) { self.kind.adjust_last_line_count(adjust); } } #[derive(Debug)] enum TargetKind { Term { term: Term, last_line_count: usize, rate_limiter: RateLimiter, draw_state: DrawState, }, Multi { state: Arc>, idx: usize, }, Hidden, TermLike { inner: Box, last_line_count: usize, rate_limiter: Option, draw_state: DrawState, }, } impl TargetKind { /// Adjust `last_line_count` such that the next draw operation keeps/clears additional lines fn adjust_last_line_count(&mut self, adjust: LineAdjust) { let last_line_count: &mut usize = match self { Self::Term { last_line_count, .. } => last_line_count, Self::TermLike { last_line_count, .. } => last_line_count, _ => return, }; match adjust { LineAdjust::Clear(count) => *last_line_count = last_line_count.saturating_add(count), LineAdjust::Keep(count) => *last_line_count = last_line_count.saturating_sub(count), } } } pub(crate) enum Drawable<'a> { Term { term: &'a Term, last_line_count: &'a mut usize, draw_state: &'a mut DrawState, }, Multi { state: RwLockWriteGuard<'a, MultiState>, idx: usize, force_draw: bool, now: Instant, }, TermLike { term_like: &'a dyn TermLike, last_line_count: &'a mut usize, draw_state: &'a mut DrawState, }, } impl<'a> Drawable<'a> { /// Adjust `last_line_count` such that the next draw operation keeps/clears additional lines pub(crate) fn adjust_last_line_count(&mut self, adjust: LineAdjust) { let last_line_count: &mut usize = match self { Drawable::Term { last_line_count, .. } => last_line_count, Drawable::TermLike { last_line_count, .. } => last_line_count, _ => return, }; match adjust { LineAdjust::Clear(count) => *last_line_count = last_line_count.saturating_add(count), LineAdjust::Keep(count) => *last_line_count = last_line_count.saturating_sub(count), } } pub(crate) fn state(&mut self) -> DrawStateWrapper<'_> { let mut state = match self { Drawable::Term { draw_state, .. } => DrawStateWrapper::for_term(draw_state), Drawable::Multi { state, idx, .. } => state.draw_state(*idx), Drawable::TermLike { draw_state, .. } => DrawStateWrapper::for_term(draw_state), }; state.reset(); state } pub(crate) fn clear(mut self) -> io::Result<()> { let state = self.state(); drop(state); self.draw() } pub(crate) fn draw(self) -> io::Result<()> { match self { Drawable::Term { term, last_line_count, draw_state, } => draw_state.draw_to_term(term, last_line_count), Drawable::Multi { mut state, force_draw, now, .. } => state.draw(force_draw, None, now), Drawable::TermLike { term_like, last_line_count, draw_state, } => draw_state.draw_to_term(term_like, last_line_count), } } } pub(crate) enum LineAdjust { /// Adds to `last_line_count` so that the next draw also clears those lines Clear(usize), /// Subtracts from `last_line_count` so that the next draw retains those lines Keep(usize), } pub(crate) struct DrawStateWrapper<'a> { state: &'a mut DrawState, orphan_lines: Option<&'a mut Vec>, } impl<'a> DrawStateWrapper<'a> { pub(crate) fn for_term(state: &'a mut DrawState) -> Self { Self { state, orphan_lines: None, } } pub(crate) fn for_multi(state: &'a mut DrawState, orphan_lines: &'a mut Vec) -> Self { Self { state, orphan_lines: Some(orphan_lines), } } } impl std::ops::Deref for DrawStateWrapper<'_> { type Target = DrawState; fn deref(&self) -> &Self::Target { self.state } } impl std::ops::DerefMut for DrawStateWrapper<'_> { fn deref_mut(&mut self) -> &mut Self::Target { self.state } } impl Drop for DrawStateWrapper<'_> { fn drop(&mut self) { if let Some(orphaned) = &mut self.orphan_lines { orphaned.extend(self.state.lines.drain(..self.state.orphan_lines_count)); self.state.orphan_lines_count = 0; } } } #[derive(Debug)] struct RateLimiter { interval: u16, // in milliseconds capacity: u8, prev: Instant, } /// Rate limit but allow occasional bursts above desired rate impl RateLimiter { fn new(rate: u8) -> Self { Self { interval: 1000 / (rate as u16), // between 3 and 1000 milliseconds capacity: MAX_BURST, prev: Instant::now(), } } fn allow(&mut self, now: Instant) -> bool { if now < self.prev { return false; } let elapsed = now - self.prev; // If `capacity` is 0 and not enough time (`self.interval` ms) has passed since // `self.prev` to add new capacity, return `false`. The goal of this method is to // make this decision as efficient as possible. if self.capacity == 0 && elapsed < Duration::from_millis(self.interval as u64) { return false; } // We now calculate `new`, the number of ms, since we last returned `true`, // and `remainder`, which represents a number of ns less than 1ms which we cannot // convert into capacity now, so we're saving it for later. let (new, remainder) = ( elapsed.as_millis() / self.interval as u128, elapsed.as_nanos() % (self.interval as u128 * 1_000_000), ); // We add `new` to `capacity`, subtract one for returning `true` from here, // then make sure it does not exceed a maximum of `MAX_BURST`, then store it. self.capacity = Ord::min(MAX_BURST as u128, (self.capacity as u128) + new - 1) as u8; // Store `prev` for the next iteration after subtracting the `remainder`. // Just use `unwrap` here because it shouldn't be possible for this to underflow. self.prev = now .checked_sub(Duration::from_nanos(remainder as u64)) .unwrap(); true } } const MAX_BURST: u8 = 20; /// The drawn state of an element. #[derive(Clone, Debug, Default)] pub(crate) struct DrawState { /// The lines to print (can contain ANSI codes) pub(crate) lines: Vec, /// The number of lines that shouldn't be reaped by the next tick. pub(crate) orphan_lines_count: usize, /// True if we should move the cursor up when possible instead of clearing lines. pub(crate) move_cursor: bool, /// Controls how the multi progress is aligned if some of its progress bars get removed, default is `Top` pub(crate) alignment: MultiProgressAlignment, } impl DrawState { fn draw_to_term( &mut self, term: &(impl TermLike + ?Sized), last_line_count: &mut usize, ) -> io::Result<()> { if panicking() { return Ok(()); } if !self.lines.is_empty() && self.move_cursor { term.move_cursor_up(*last_line_count)?; } else { // Fork of console::clear_last_lines that assumes that the last line doesn't contain a '\n' let n = *last_line_count; term.move_cursor_up(n.saturating_sub(1))?; for i in 0..n { term.clear_line()?; if i + 1 != n { term.move_cursor_down(1)?; } } term.move_cursor_up(n.saturating_sub(1))?; } let shift = match self.alignment { MultiProgressAlignment::Bottom if self.lines.len() < *last_line_count => { let shift = *last_line_count - self.lines.len(); for _ in 0..shift { term.write_line("")?; } shift } _ => 0, }; let term_height = term.height() as usize; let term_width = term.width() as usize; let len = self.lines.len(); let mut real_len = 0; let mut last_line_filler = 0; debug_assert!(self.orphan_lines_count <= self.lines.len()); for (idx, line) in self.lines.iter().enumerate() { let line_width = console::measure_text_width(line); let diff = if line.is_empty() { // Empty line are new line 1 } else { // Calculate real length based on terminal width // This take in account linewrap from terminal let terminal_len = (line_width as f64 / term_width as f64).ceil() as usize; // If the line is effectively empty (for example when it consists // solely of ANSI color code sequences, count it the same as a // new line. If the line is measured to be len = 0, we will // subtract with overflow later. usize::max(terminal_len, 1) }; // Don't consider orphan lines when comparing to terminal height. debug_assert!(idx <= real_len); if self.orphan_lines_count <= idx && real_len - self.orphan_lines_count + diff > term_height { break; } real_len += diff; if idx != 0 { term.write_line("")?; } term.write_str(line)?; if idx + 1 == len { // Keep the cursor on the right terminal side // So that next user writes/prints will happen on the next line last_line_filler = term_width.saturating_sub(line_width); } } term.write_str(&" ".repeat(last_line_filler))?; term.flush()?; *last_line_count = real_len - self.orphan_lines_count + shift; Ok(()) } fn reset(&mut self) { self.lines.clear(); self.orphan_lines_count = 0; } } #[cfg(test)] mod tests { use crate::{MultiProgress, ProgressBar, ProgressDrawTarget}; #[test] fn multi_is_hidden() { let mp = MultiProgress::with_draw_target(ProgressDrawTarget::hidden()); let pb = mp.add(ProgressBar::new(100)); assert!(mp.is_hidden()); assert!(pb.is_hidden()); } } indicatif-0.17.7/src/format.rs000064400000000000000000000304311046102023000143000ustar 00000000000000use std::fmt; use std::time::Duration; use number_prefix::NumberPrefix; const SECOND: Duration = Duration::from_secs(1); const MINUTE: Duration = Duration::from_secs(60); const HOUR: Duration = Duration::from_secs(60 * 60); const DAY: Duration = Duration::from_secs(24 * 60 * 60); const WEEK: Duration = Duration::from_secs(7 * 24 * 60 * 60); const YEAR: Duration = Duration::from_secs(365 * 24 * 60 * 60); /// Wraps an std duration for human basic formatting. #[derive(Debug)] pub struct FormattedDuration(pub Duration); /// Wraps an std duration for human readable formatting. #[derive(Debug)] pub struct HumanDuration(pub Duration); /// Formats bytes for human readability #[derive(Debug)] pub struct HumanBytes(pub u64); /// Formats bytes for human readability using SI prefixes #[derive(Debug)] pub struct DecimalBytes(pub u64); /// Formats bytes for human readability using ISO/IEC prefixes #[derive(Debug)] pub struct BinaryBytes(pub u64); /// Formats counts for human readability using commas #[derive(Debug)] pub struct HumanCount(pub u64); /// Formats counts for human readability using commas for floats #[derive(Debug)] pub struct HumanFloatCount(pub f64); impl fmt::Display for FormattedDuration { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut t = self.0.as_secs(); let seconds = t % 60; t /= 60; let minutes = t % 60; t /= 60; let hours = t % 24; t /= 24; if t > 0 { let days = t; write!(f, "{days}d {hours:02}:{minutes:02}:{seconds:02}") } else { write!(f, "{hours:02}:{minutes:02}:{seconds:02}") } } } // `HumanDuration` should be as intuitively understandable as possible. // So we want to round, not truncate: otherwise 1 hour and 59 minutes // would display an ETA of "1 hour" which underestimates the time // remaining by a factor 2. // // To make the precision more uniform, we avoid displaying "1 unit" // (except for seconds), because it would be displayed for a relatively // long duration compared to the unit itself. Instead, when we arrive // around 1.5 unit, we change from "2 units" to the next smaller unit // (e.g. "89 seconds"). // // Formally: // * for n >= 2, we go from "n+1 units" to "n units" exactly at (n + 1/2) units // * we switch from "2 units" to the next smaller unit at (1.5 unit minus half of the next smaller unit) impl fmt::Display for HumanDuration { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut idx = 0; for (i, &(cur, _, _)) in UNITS.iter().enumerate() { idx = i; match UNITS.get(i + 1) { Some(&next) if self.0.saturating_add(next.0 / 2) >= cur + cur / 2 => break, _ => continue, } } let (unit, name, alt) = UNITS[idx]; // FIXME when `div_duration_f64` is stable let mut t = (self.0.as_secs_f64() / unit.as_secs_f64()).round() as usize; if idx < UNITS.len() - 1 { t = Ord::max(t, 2); } match (f.alternate(), t) { (true, _) => write!(f, "{t}{alt}"), (false, 1) => write!(f, "{t} {name}"), (false, _) => write!(f, "{t} {name}s"), } } } const UNITS: &[(Duration, &str, &str)] = &[ (YEAR, "year", "y"), (WEEK, "week", "w"), (DAY, "day", "d"), (HOUR, "hour", "h"), (MINUTE, "minute", "m"), (SECOND, "second", "s"), ]; impl fmt::Display for HumanBytes { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match NumberPrefix::binary(self.0 as f64) { NumberPrefix::Standalone(number) => write!(f, "{number:.0} B"), NumberPrefix::Prefixed(prefix, number) => write!(f, "{number:.2} {prefix}B"), } } } impl fmt::Display for DecimalBytes { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match NumberPrefix::decimal(self.0 as f64) { NumberPrefix::Standalone(number) => write!(f, "{number:.0} B"), NumberPrefix::Prefixed(prefix, number) => write!(f, "{number:.2} {prefix}B"), } } } impl fmt::Display for BinaryBytes { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match NumberPrefix::binary(self.0 as f64) { NumberPrefix::Standalone(number) => write!(f, "{number:.0} B"), NumberPrefix::Prefixed(prefix, number) => write!(f, "{number:.2} {prefix}B"), } } } impl fmt::Display for HumanCount { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use fmt::Write; let num = self.0.to_string(); let len = num.len(); for (idx, c) in num.chars().enumerate() { let pos = len - idx - 1; f.write_char(c)?; if pos > 0 && pos % 3 == 0 { f.write_char(',')?; } } Ok(()) } } impl fmt::Display for HumanFloatCount { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use fmt::Write; let num = format!("{:.4}", self.0); let (int_part, frac_part) = match num.split_once('.') { Some((int_str, fract_str)) => (int_str.to_string(), fract_str), None => (self.0.trunc().to_string(), ""), }; let len = int_part.len(); for (idx, c) in int_part.chars().enumerate() { let pos = len - idx - 1; f.write_char(c)?; if pos > 0 && pos % 3 == 0 { f.write_char(',')?; } } let frac_trimmed = frac_part.trim_end_matches('0'); if !frac_trimmed.is_empty() { f.write_char('.')?; f.write_str(frac_trimmed)?; } Ok(()) } } #[cfg(test)] mod tests { use super::*; const MILLI: Duration = Duration::from_millis(1); #[test] fn human_duration_alternate() { for (unit, _, alt) in UNITS { assert_eq!(format!("2{alt}"), format!("{:#}", HumanDuration(2 * *unit))); } } #[test] fn human_duration_less_than_one_second() { assert_eq!( "0 seconds", format!("{}", HumanDuration(Duration::from_secs(0))) ); assert_eq!("0 seconds", format!("{}", HumanDuration(MILLI))); assert_eq!("0 seconds", format!("{}", HumanDuration(499 * MILLI))); assert_eq!("1 second", format!("{}", HumanDuration(500 * MILLI))); assert_eq!("1 second", format!("{}", HumanDuration(999 * MILLI))); } #[test] fn human_duration_less_than_two_seconds() { assert_eq!("1 second", format!("{}", HumanDuration(1499 * MILLI))); assert_eq!("2 seconds", format!("{}", HumanDuration(1500 * MILLI))); assert_eq!("2 seconds", format!("{}", HumanDuration(1999 * MILLI))); } #[test] fn human_duration_one_unit() { assert_eq!("1 second", format!("{}", HumanDuration(SECOND))); assert_eq!("60 seconds", format!("{}", HumanDuration(MINUTE))); assert_eq!("60 minutes", format!("{}", HumanDuration(HOUR))); assert_eq!("24 hours", format!("{}", HumanDuration(DAY))); assert_eq!("7 days", format!("{}", HumanDuration(WEEK))); assert_eq!("52 weeks", format!("{}", HumanDuration(YEAR))); } #[test] fn human_duration_less_than_one_and_a_half_unit() { // this one is actually done at 1.5 unit - half of the next smaller unit - epsilon // and should display the next smaller unit let d = HumanDuration(MINUTE + MINUTE / 2 - SECOND / 2 - MILLI); assert_eq!("89 seconds", format!("{d}")); let d = HumanDuration(HOUR + HOUR / 2 - MINUTE / 2 - MILLI); assert_eq!("89 minutes", format!("{d}")); let d = HumanDuration(DAY + DAY / 2 - HOUR / 2 - MILLI); assert_eq!("35 hours", format!("{d}")); let d = HumanDuration(WEEK + WEEK / 2 - DAY / 2 - MILLI); assert_eq!("10 days", format!("{d}")); let d = HumanDuration(YEAR + YEAR / 2 - WEEK / 2 - MILLI); assert_eq!("78 weeks", format!("{d}")); } #[test] fn human_duration_one_and_a_half_unit() { // this one is actually done at 1.5 unit - half of the next smaller unit // and should still display "2 units" let d = HumanDuration(MINUTE + MINUTE / 2 - SECOND / 2); assert_eq!("2 minutes", format!("{d}")); let d = HumanDuration(HOUR + HOUR / 2 - MINUTE / 2); assert_eq!("2 hours", format!("{d}")); let d = HumanDuration(DAY + DAY / 2 - HOUR / 2); assert_eq!("2 days", format!("{d}")); let d = HumanDuration(WEEK + WEEK / 2 - DAY / 2); assert_eq!("2 weeks", format!("{d}")); let d = HumanDuration(YEAR + YEAR / 2 - WEEK / 2); assert_eq!("2 years", format!("{d}")); } #[test] fn human_duration_two_units() { assert_eq!("2 seconds", format!("{}", HumanDuration(2 * SECOND))); assert_eq!("2 minutes", format!("{}", HumanDuration(2 * MINUTE))); assert_eq!("2 hours", format!("{}", HumanDuration(2 * HOUR))); assert_eq!("2 days", format!("{}", HumanDuration(2 * DAY))); assert_eq!("2 weeks", format!("{}", HumanDuration(2 * WEEK))); assert_eq!("2 years", format!("{}", HumanDuration(2 * YEAR))); } #[test] fn human_duration_less_than_two_and_a_half_units() { let d = HumanDuration(2 * SECOND + SECOND / 2 - MILLI); assert_eq!("2 seconds", format!("{d}")); let d = HumanDuration(2 * MINUTE + MINUTE / 2 - MILLI); assert_eq!("2 minutes", format!("{d}")); let d = HumanDuration(2 * HOUR + HOUR / 2 - MILLI); assert_eq!("2 hours", format!("{d}")); let d = HumanDuration(2 * DAY + DAY / 2 - MILLI); assert_eq!("2 days", format!("{d}")); let d = HumanDuration(2 * WEEK + WEEK / 2 - MILLI); assert_eq!("2 weeks", format!("{d}")); let d = HumanDuration(2 * YEAR + YEAR / 2 - MILLI); assert_eq!("2 years", format!("{d}")); } #[test] fn human_duration_two_and_a_half_units() { let d = HumanDuration(2 * SECOND + SECOND / 2); assert_eq!("3 seconds", format!("{d}")); let d = HumanDuration(2 * MINUTE + MINUTE / 2); assert_eq!("3 minutes", format!("{d}")); let d = HumanDuration(2 * HOUR + HOUR / 2); assert_eq!("3 hours", format!("{d}")); let d = HumanDuration(2 * DAY + DAY / 2); assert_eq!("3 days", format!("{d}")); let d = HumanDuration(2 * WEEK + WEEK / 2); assert_eq!("3 weeks", format!("{d}")); let d = HumanDuration(2 * YEAR + YEAR / 2); assert_eq!("3 years", format!("{d}")); } #[test] fn human_duration_three_units() { assert_eq!("3 seconds", format!("{}", HumanDuration(3 * SECOND))); assert_eq!("3 minutes", format!("{}", HumanDuration(3 * MINUTE))); assert_eq!("3 hours", format!("{}", HumanDuration(3 * HOUR))); assert_eq!("3 days", format!("{}", HumanDuration(3 * DAY))); assert_eq!("3 weeks", format!("{}", HumanDuration(3 * WEEK))); assert_eq!("3 years", format!("{}", HumanDuration(3 * YEAR))); } #[test] fn human_count() { assert_eq!("42", format!("{}", HumanCount(42))); assert_eq!("7,654", format!("{}", HumanCount(7654))); assert_eq!("12,345", format!("{}", HumanCount(12345))); assert_eq!("1,234,567,890", format!("{}", HumanCount(1234567890))); } #[test] fn human_float_count() { assert_eq!("42", format!("{}", HumanFloatCount(42.0))); assert_eq!("7,654", format!("{}", HumanFloatCount(7654.0))); assert_eq!("12,345", format!("{}", HumanFloatCount(12345.0))); assert_eq!( "1,234,567,890", format!("{}", HumanFloatCount(1234567890.0)) ); assert_eq!("42.5", format!("{}", HumanFloatCount(42.5))); assert_eq!("42.5", format!("{}", HumanFloatCount(42.500012345))); assert_eq!("42.502", format!("{}", HumanFloatCount(42.502012345))); assert_eq!("7,654.321", format!("{}", HumanFloatCount(7654.321))); assert_eq!("7,654.321", format!("{}", HumanFloatCount(7654.3210123456))); assert_eq!("12,345.6789", format!("{}", HumanFloatCount(12345.6789))); assert_eq!( "1,234,567,890.1235", format!("{}", HumanFloatCount(1234567890.1234567)) ); assert_eq!( "1,234,567,890.1234", format!("{}", HumanFloatCount(1234567890.1234321)) ); } } indicatif-0.17.7/src/in_memory.rs000064400000000000000000000257611046102023000150200ustar 00000000000000use std::fmt::{Debug, Formatter, Write as _}; use std::io::Write as _; use std::sync::{Arc, Mutex}; use vt100::Parser; use crate::TermLike; /// A thin wrapper around [`vt100::Parser`]. /// /// This is just an [`Arc`] around its internal state, so it can be freely cloned. #[cfg_attr(docsrs, doc(cfg(feature = "in_memory")))] #[derive(Debug, Clone)] pub struct InMemoryTerm { state: Arc>, } impl InMemoryTerm { pub fn new(rows: u16, cols: u16) -> InMemoryTerm { assert!(rows > 0, "rows must be > 0"); assert!(cols > 0, "cols must be > 0"); InMemoryTerm { state: Arc::new(Mutex::new(InMemoryTermState::new(rows, cols))), } } pub fn reset(&self) { let mut state = self.state.lock().unwrap(); *state = InMemoryTermState::new(state.height, state.width); } pub fn contents(&self) -> String { let state = self.state.lock().unwrap(); // For some reason, the `Screen::contents` method doesn't include newlines in what it // returns, making it useless for our purposes. So we need to manually reconstruct the // contents by iterating over the rows in the terminal buffer. let mut rows = state .parser .screen() .rows(0, state.width) .collect::>(); // Reverse the rows and trim empty lines from the end rows = rows .into_iter() .rev() .skip_while(|line| line.is_empty()) .map(|line| line.trim_end().to_string()) .collect(); // Un-reverse the rows and join them up with newlines rows.reverse(); rows.join("\n") } pub fn contents_formatted(&self) -> Vec { let state = self.state.lock().unwrap(); // For some reason, the `Screen::contents` method doesn't include newlines in what it // returns, making it useless for our purposes. So we need to manually reconstruct the // contents by iterating over the rows in the terminal buffer. let mut rows = state .parser .screen() .rows_formatted(0, state.width) .collect::>(); // Reverse the rows and trim empty lines from the end rows = rows .into_iter() .rev() .skip_while(|line| line.is_empty()) .collect(); // Un-reverse the rows rows.reverse(); // Calculate buffer size let reset = b""; let len = rows.iter().map(|line| line.len() + reset.len() + 1).sum(); // Join rows up with reset codes and newlines let mut contents = rows.iter().fold(Vec::with_capacity(len), |mut acc, cur| { acc.extend_from_slice(cur); acc.extend_from_slice(reset); acc.push(b'\n'); acc }); // Remove last newline again, but leave the reset code contents.truncate(len.saturating_sub(1)); contents } pub fn moves_since_last_check(&self) -> String { let mut s = String::new(); for line in std::mem::take(&mut self.state.lock().unwrap().history) { writeln!(s, "{line:?}").unwrap(); } s } } impl TermLike for InMemoryTerm { fn width(&self) -> u16 { self.state.lock().unwrap().width } fn height(&self) -> u16 { self.state.lock().unwrap().height } fn move_cursor_up(&self, n: usize) -> std::io::Result<()> { match n { 0 => Ok(()), _ => { let mut state = self.state.lock().unwrap(); state.history.push(Move::Up(n)); state.write_str(&format!("\x1b[{n}A")) } } } fn move_cursor_down(&self, n: usize) -> std::io::Result<()> { match n { 0 => Ok(()), _ => { let mut state = self.state.lock().unwrap(); state.history.push(Move::Down(n)); state.write_str(&format!("\x1b[{n}B")) } } } fn move_cursor_right(&self, n: usize) -> std::io::Result<()> { match n { 0 => Ok(()), _ => { let mut state = self.state.lock().unwrap(); state.history.push(Move::Right(n)); state.write_str(&format!("\x1b[{n}C")) } } } fn move_cursor_left(&self, n: usize) -> std::io::Result<()> { match n { 0 => Ok(()), _ => { let mut state = self.state.lock().unwrap(); state.history.push(Move::Left(n)); state.write_str(&format!("\x1b[{n}D")) } } } fn write_line(&self, s: &str) -> std::io::Result<()> { let mut state = self.state.lock().unwrap(); state.history.push(Move::Str(s.into())); state.history.push(Move::NewLine); // Don't try to handle writing lines with additional newlines embedded in them - it's not // worth the extra code for something that indicatif doesn't even do. May revisit in future. debug_assert!( s.lines().count() <= 1, "calling write_line with embedded newlines is not allowed" ); // vte100 needs the full \r\n sequence to jump to the next line and reset the cursor to // the beginning of the line. Be flexible and take either \n or \r\n state.write_str(s)?; state.write_str("\r\n") } fn write_str(&self, s: &str) -> std::io::Result<()> { let mut state = self.state.lock().unwrap(); state.history.push(Move::Str(s.into())); state.write_str(s) } fn clear_line(&self) -> std::io::Result<()> { let mut state = self.state.lock().unwrap(); state.history.push(Move::Clear); state.write_str("\r\x1b[2K") } fn flush(&self) -> std::io::Result<()> { let mut state = self.state.lock().unwrap(); state.history.push(Move::Flush); state.parser.flush() } } struct InMemoryTermState { width: u16, height: u16, parser: vt100::Parser, history: Vec, } impl InMemoryTermState { pub(crate) fn new(rows: u16, cols: u16) -> InMemoryTermState { InMemoryTermState { width: cols, height: rows, parser: Parser::new(rows, cols, 0), history: vec![], } } pub(crate) fn write_str(&mut self, s: &str) -> std::io::Result<()> { self.parser.write_all(s.as_bytes()) } } impl Debug for InMemoryTermState { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("InMemoryTermState").finish_non_exhaustive() } } #[derive(Debug, PartialEq, Clone)] enum Move { Up(usize), Down(usize), Left(usize), Right(usize), Str(String), NewLine, Clear, Flush, } #[cfg(test)] mod test { use super::*; fn cursor_pos(in_mem: &InMemoryTerm) -> (u16, u16) { in_mem .state .lock() .unwrap() .parser .screen() .cursor_position() } #[test] fn line_wrapping() { let in_mem = InMemoryTerm::new(10, 5); assert_eq!(cursor_pos(&in_mem), (0, 0)); in_mem.write_str("ABCDE").unwrap(); assert_eq!(in_mem.contents(), "ABCDE"); assert_eq!(cursor_pos(&in_mem), (0, 5)); assert_eq!( in_mem.moves_since_last_check(), r#"Str("ABCDE") "# ); // Should wrap onto next line in_mem.write_str("FG").unwrap(); assert_eq!(in_mem.contents(), "ABCDE\nFG"); assert_eq!(cursor_pos(&in_mem), (1, 2)); assert_eq!( in_mem.moves_since_last_check(), r#"Str("FG") "# ); in_mem.write_str("HIJ").unwrap(); assert_eq!(in_mem.contents(), "ABCDE\nFGHIJ"); assert_eq!(cursor_pos(&in_mem), (1, 5)); assert_eq!( in_mem.moves_since_last_check(), r#"Str("HIJ") "# ); } #[test] fn write_line() { let in_mem = InMemoryTerm::new(10, 5); assert_eq!(cursor_pos(&in_mem), (0, 0)); in_mem.write_line("A").unwrap(); assert_eq!(in_mem.contents(), "A"); assert_eq!(cursor_pos(&in_mem), (1, 0)); assert_eq!( in_mem.moves_since_last_check(), r#"Str("A") NewLine "# ); in_mem.write_line("B").unwrap(); assert_eq!(in_mem.contents(), "A\nB"); assert_eq!(cursor_pos(&in_mem), (2, 0)); assert_eq!( in_mem.moves_since_last_check(), r#"Str("B") NewLine "# ); in_mem.write_line("Longer than cols").unwrap(); assert_eq!(in_mem.contents(), "A\nB\nLonge\nr tha\nn col\ns"); assert_eq!(cursor_pos(&in_mem), (6, 0)); assert_eq!( in_mem.moves_since_last_check(), r#"Str("Longer than cols") NewLine "# ); } #[test] fn basic_functionality() { let in_mem = InMemoryTerm::new(10, 80); in_mem.write_line("This is a test line").unwrap(); assert_eq!(in_mem.contents(), "This is a test line"); assert_eq!( in_mem.moves_since_last_check(), r#"Str("This is a test line") NewLine "# ); in_mem.write_line("And another line!").unwrap(); assert_eq!(in_mem.contents(), "This is a test line\nAnd another line!"); assert_eq!( in_mem.moves_since_last_check(), r#"Str("And another line!") NewLine "# ); in_mem.move_cursor_up(1).unwrap(); in_mem.write_str("TEST").unwrap(); assert_eq!(in_mem.contents(), "This is a test line\nTESTanother line!"); assert_eq!( in_mem.moves_since_last_check(), r#"Up(1) Str("TEST") "# ); } #[test] fn newlines() { let in_mem = InMemoryTerm::new(10, 10); in_mem.write_line("LINE ONE").unwrap(); in_mem.write_line("LINE TWO").unwrap(); in_mem.write_line("").unwrap(); in_mem.write_line("LINE FOUR").unwrap(); assert_eq!(in_mem.contents(), "LINE ONE\nLINE TWO\n\nLINE FOUR"); assert_eq!( in_mem.moves_since_last_check(), r#"Str("LINE ONE") NewLine Str("LINE TWO") NewLine Str("") NewLine Str("LINE FOUR") NewLine "# ); } #[test] fn cursor_zero_movement() { let in_mem = InMemoryTerm::new(10, 80); in_mem.write_line("LINE ONE").unwrap(); assert_eq!(cursor_pos(&in_mem), (1, 0)); // Check that moving zero rows/cols does not actually move cursor in_mem.move_cursor_up(0).unwrap(); assert_eq!(cursor_pos(&in_mem), (1, 0)); in_mem.move_cursor_down(0).unwrap(); assert_eq!(cursor_pos(&in_mem), (1, 0)); in_mem.move_cursor_right(1).unwrap(); assert_eq!(cursor_pos(&in_mem), (1, 1)); in_mem.move_cursor_left(0).unwrap(); assert_eq!(cursor_pos(&in_mem), (1, 1)); in_mem.move_cursor_right(0).unwrap(); assert_eq!(cursor_pos(&in_mem), (1, 1)); } } indicatif-0.17.7/src/iter.rs000064400000000000000000000253201046102023000137540ustar 00000000000000use std::borrow::Cow; use std::io::{self, IoSliceMut}; use std::iter::FusedIterator; #[cfg(feature = "tokio")] use std::pin::Pin; #[cfg(feature = "tokio")] use std::task::{Context, Poll}; use std::time::Duration; #[cfg(feature = "tokio")] use tokio::io::{ReadBuf, SeekFrom}; use crate::progress_bar::ProgressBar; use crate::state::ProgressFinish; use crate::style::ProgressStyle; /// Wraps an iterator to display its progress. pub trait ProgressIterator where Self: Sized + Iterator, { /// Wrap an iterator with default styling. Uses `Iterator::size_hint` to get length. /// Returns `Some(..)` only if `size_hint.1` is `Some`. If you want to create a progress bar /// even if `size_hint.1` returns `None` use `progress_count` or `progress_with` instead. fn try_progress(self) -> Option> { self.size_hint() .1 .map(|len| self.progress_count(u64::try_from(len).unwrap())) } /// Wrap an iterator with default styling. fn progress(self) -> ProgressBarIter where Self: ExactSizeIterator, { let len = u64::try_from(self.len()).unwrap(); self.progress_count(len) } /// Wrap an iterator with an explicit element count. fn progress_count(self, len: u64) -> ProgressBarIter { self.progress_with(ProgressBar::new(len)) } /// Wrap an iterator with a custom progress bar. fn progress_with(self, progress: ProgressBar) -> ProgressBarIter; /// Wrap an iterator with a progress bar and style it. fn progress_with_style(self, style: crate::ProgressStyle) -> ProgressBarIter where Self: ExactSizeIterator, { let len = u64::try_from(self.len()).unwrap(); let bar = ProgressBar::new(len).with_style(style); self.progress_with(bar) } } /// Wraps an iterator to display its progress. #[derive(Debug)] pub struct ProgressBarIter { pub(crate) it: T, pub progress: ProgressBar, } impl ProgressBarIter { /// Builder-like function for setting underlying progress bar's style. /// /// See [ProgressBar::with_style]. pub fn with_style(mut self, style: ProgressStyle) -> Self { self.progress = self.progress.with_style(style); self } /// Builder-like function for setting underlying progress bar's prefix. /// /// See [ProgressBar::with_prefix]. pub fn with_prefix(mut self, prefix: impl Into>) -> Self { self.progress = self.progress.with_prefix(prefix); self } /// Builder-like function for setting underlying progress bar's message. /// /// See [ProgressBar::with_message]. pub fn with_message(mut self, message: impl Into>) -> Self { self.progress = self.progress.with_message(message); self } /// Builder-like function for setting underlying progress bar's position. /// /// See [ProgressBar::with_position]. pub fn with_position(mut self, position: u64) -> Self { self.progress = self.progress.with_position(position); self } /// Builder-like function for setting underlying progress bar's elapsed time. /// /// See [ProgressBar::with_elapsed]. pub fn with_elapsed(mut self, elapsed: Duration) -> Self { self.progress = self.progress.with_elapsed(elapsed); self } /// Builder-like function for setting underlying progress bar's finish behavior. /// /// See [ProgressBar::with_finish]. pub fn with_finish(mut self, finish: ProgressFinish) -> Self { self.progress = self.progress.with_finish(finish); self } } impl> Iterator for ProgressBarIter { type Item = S; fn next(&mut self) -> Option { let item = self.it.next(); if item.is_some() { self.progress.inc(1); } else if !self.progress.is_finished() { self.progress.finish_using_style(); } item } } impl ExactSizeIterator for ProgressBarIter { fn len(&self) -> usize { self.it.len() } } impl DoubleEndedIterator for ProgressBarIter { fn next_back(&mut self) -> Option { let item = self.it.next_back(); if item.is_some() { self.progress.inc(1); } else if !self.progress.is_finished() { self.progress.finish_using_style(); } item } } impl FusedIterator for ProgressBarIter {} impl io::Read for ProgressBarIter { fn read(&mut self, buf: &mut [u8]) -> io::Result { let inc = self.it.read(buf)?; self.progress.inc(inc as u64); Ok(inc) } fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { let inc = self.it.read_vectored(bufs)?; self.progress.inc(inc as u64); Ok(inc) } fn read_to_string(&mut self, buf: &mut String) -> io::Result { let inc = self.it.read_to_string(buf)?; self.progress.inc(inc as u64); Ok(inc) } fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { self.it.read_exact(buf)?; self.progress.inc(buf.len() as u64); Ok(()) } } impl io::BufRead for ProgressBarIter { fn fill_buf(&mut self) -> io::Result<&[u8]> { self.it.fill_buf() } fn consume(&mut self, amt: usize) { self.it.consume(amt); self.progress.inc(amt as u64); } } impl io::Seek for ProgressBarIter { fn seek(&mut self, f: io::SeekFrom) -> io::Result { self.it.seek(f).map(|pos| { self.progress.set_position(pos); pos }) } // Pass this through to preserve optimizations that the inner I/O object may use here // Also avoid sending a set_position update when the position hasn't changed fn stream_position(&mut self) -> io::Result { self.it.stream_position() } } #[cfg(feature = "tokio")] #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] impl tokio::io::AsyncWrite for ProgressBarIter { fn poll_write( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { Pin::new(&mut self.it).poll_write(cx, buf).map(|poll| { poll.map(|inc| { self.progress.inc(inc as u64); inc }) }) } fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut self.it).poll_flush(cx) } fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut self.it).poll_shutdown(cx) } } #[cfg(feature = "tokio")] #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] impl tokio::io::AsyncRead for ProgressBarIter { fn poll_read( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>, ) -> Poll> { let prev_len = buf.filled().len() as u64; if let Poll::Ready(e) = Pin::new(&mut self.it).poll_read(cx, buf) { self.progress.inc(buf.filled().len() as u64 - prev_len); Poll::Ready(e) } else { Poll::Pending } } } #[cfg(feature = "tokio")] #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] impl tokio::io::AsyncSeek for ProgressBarIter { fn start_seek(mut self: Pin<&mut Self>, position: SeekFrom) -> io::Result<()> { Pin::new(&mut self.it).start_seek(position) } fn poll_complete(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut self.it).poll_complete(cx) } } #[cfg(feature = "tokio")] #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] impl tokio::io::AsyncBufRead for ProgressBarIter { fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let this = self.get_mut(); let result = Pin::new(&mut this.it).poll_fill_buf(cx); if let Poll::Ready(Ok(buf)) = &result { this.progress.inc(buf.len() as u64); } result } fn consume(mut self: Pin<&mut Self>, amt: usize) { Pin::new(&mut self.it).consume(amt); } } #[cfg(feature = "futures")] #[cfg_attr(docsrs, doc(cfg(feature = "futures")))] impl futures_core::Stream for ProgressBarIter { type Item = S::Item; fn poll_next( self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll> { let this = self.get_mut(); let item = std::pin::Pin::new(&mut this.it).poll_next(cx); match &item { std::task::Poll::Ready(Some(_)) => this.progress.inc(1), std::task::Poll::Ready(None) => this.progress.finish_using_style(), std::task::Poll::Pending => {} } item } } impl io::Write for ProgressBarIter { fn write(&mut self, buf: &[u8]) -> io::Result { self.it.write(buf).map(|inc| { self.progress.inc(inc as u64); inc }) } fn write_vectored(&mut self, bufs: &[io::IoSlice]) -> io::Result { self.it.write_vectored(bufs).map(|inc| { self.progress.inc(inc as u64); inc }) } fn flush(&mut self) -> io::Result<()> { self.it.flush() } // write_fmt can not be captured with reasonable effort. // as it uses write_all internally by default that should not be a problem. // fn write_fmt(&mut self, fmt: fmt::Arguments) -> io::Result<()>; } impl> ProgressIterator for T { fn progress_with(self, progress: ProgressBar) -> ProgressBarIter { ProgressBarIter { it: self, progress } } } #[cfg(test)] mod test { use crate::iter::{ProgressBarIter, ProgressIterator}; use crate::progress_bar::ProgressBar; use crate::ProgressStyle; #[test] fn it_can_wrap_an_iterator() { let v = [1, 2, 3]; let wrap = |it: ProgressBarIter<_>| { assert_eq!(it.map(|x| x * 2).collect::>(), vec![2, 4, 6]); }; wrap(v.iter().progress()); wrap(v.iter().progress_count(3)); wrap({ let pb = ProgressBar::new(v.len() as u64); v.iter().progress_with(pb) }); wrap({ let style = ProgressStyle::default_bar() .template("{wide_bar:.red} {percent}/100%") .unwrap(); v.iter().progress_with_style(style) }); } } indicatif-0.17.7/src/lib.rs000064400000000000000000000227711046102023000135660ustar 00000000000000//! indicatif is a library for Rust that helps you build command line //! interfaces that report progress to users. It comes with various //! tools and utilities for formatting anything that indicates progress. //! //! Platform support: //! //! * Linux //! * macOS //! * Windows (colors require Windows 10) //! //! Best paired with other libraries in the family: //! //! * [console](https://docs.rs/console) //! * [dialoguer](https://docs.rs/dialoguer) //! //! # Crate Contents //! //! * **Progress bars** //! * [`ProgressBar`](struct.ProgressBar.html) for bars and spinners //! * [`MultiProgress`](struct.MultiProgress.html) for multiple bars //! * **Data Formatting** //! * [`HumanBytes`](struct.HumanBytes.html) for formatting bytes //! * [`DecimalBytes`](struct.DecimalBytes.html) for formatting bytes using SI prefixes //! * [`BinaryBytes`](struct.BinaryBytes.html) for formatting bytes using ISO/IEC prefixes //! * [`HumanDuration`](struct.HumanDuration.html) for formatting durations //! * [`HumanCount`](struct.HumanCount.html) for formatting large counts //! * [`HumanFloatCount`](struct.HumanFloatCount.html) for formatting large float counts //! //! # Progress Bars and Spinners //! //! indicatif comes with a `ProgressBar` type that supports both bounded //! progress bar uses as well as unbounded "spinner" type progress reports. //! Progress bars are `Sync` and `Send` objects which means that they are //! internally locked and can be passed from thread to thread. //! //! Additionally a `MultiProgress` utility is provided that can manage //! rendering multiple progress bars at once (eg: from multiple threads). //! //! To whet your appetite, this is what this can look like: //! //! //! //! Progress bars are manually advanced and by default draw to stderr. //! When you are done, the progress bar can be finished either visibly //! (eg: the progress bar stays on the screen) or cleared (the progress //! bar will be removed). //! //! ```rust //! use indicatif::ProgressBar; //! //! let bar = ProgressBar::new(1000); //! for _ in 0..1000 { //! bar.inc(1); //! // ... //! } //! bar.finish(); //! ``` //! //! General progress bar behaviors: //! //! * if a non terminal is detected the progress bar will be completely //! hidden. This makes piping programs to logfiles make sense out of //! the box. //! * a progress bar only starts drawing when `set_message`, `inc`, `set_position` //! or `tick` are called. In some situations you might have to call `tick` //! once to draw it. //! * progress bars should be explicitly finished to reset the rendering //! for others. Either by also clearing them or by replacing them with //! a new message / retaining the current message. //! * the default template renders neither message nor prefix. //! //! # Iterators //! //! Similar to [tqdm](https://github.com/tqdm/tqdm), progress bars can be //! associated with an iterator. For example: //! //! ```rust //! use indicatif::ProgressIterator; //! //! for _ in (0..1000).progress() { //! // ... //! } //! ``` //! //! See the [`ProgressIterator`](trait.ProgressIterator.html) trait for more //! methods to configure the number of elements in the iterator or change //! the progress bar style. Indicatif also has optional support for parallel //! iterators with [Rayon](https://github.com/rayon-rs/rayon). In your //! `Cargo.toml`, use the "rayon" feature: //! //! ```toml //! [dependencies] //! indicatif = {version = "*", features = ["rayon"]} //! ``` //! //! And then use it like this: //! //! ```rust,ignore //! # extern crate rayon; //! use indicatif::ParallelProgressIterator; //! use rayon::iter::{ParallelIterator, IntoParallelRefIterator}; //! //! let v: Vec<_> = (0..100000).collect(); //! let v2: Vec<_> = v.par_iter().progress_count(v.len() as u64).map(|i| i + 1).collect(); //! assert_eq!(v2[0], 1); //! ``` //! //! Or if you'd like to customize the progress bar: //! //! ```rust,ignore //! # extern crate rayon; //! use indicatif::{ProgressBar, ParallelProgressIterator, ProgressStyle}; //! use rayon::iter::{ParallelIterator, IntoParallelRefIterator}; //! //! // Alternatively, use `ProgressBar::new().with_style()` //! let style = ProgressStyle::default_bar(); //! let v: Vec<_> = (0..100000).collect(); //! let v2: Vec<_> = v.par_iter().progress_with_style(style).map(|i| i + 1).collect(); //! assert_eq!(v2[0], 1); //! ``` //! //! # Templates //! //! Progress bars can be styled with simple format strings similar to the //! ones in Rust itself. The format for a placeholder is `{key:options}` //! where the `options` part is optional. If provided the format is this: //! //! ```text //! <^> for an optional alignment specification (left, center and right respectively) //! WIDTH an optional width as positive integer //! ! an optional exclamation mark to enable truncation //! .STYLE an optional dot separated style string //! /STYLE an optional dot separated alternative style string //! ``` //! //! For the style component see [`Style::from_dotted_str`](https://docs.rs/console/0.7.5/console/struct.Style.html#method.from_dotted_str) //! for more information. Indicatif uses the `console` base crate for all //! colorization and formatting options. //! //! Some examples for templates: //! //! ```text //! [{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {msg} //! ``` //! //! This sets a progress bar that is 40 characters wide and has cyan //! as primary style color and blue as alternative style color. //! Alternative styles are currently only used for progress bars. //! //! Example configuration: //! //! ```rust //! # use indicatif::{ProgressBar, ProgressStyle}; //! # let bar = ProgressBar::new(0); //! bar.set_style(ProgressStyle::with_template("[{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {msg}") //! .unwrap() //! .progress_chars("##-")); //! ``` //! //! The following keys exist: //! //! * `bar`: renders a progress bar. By default 20 characters wide. The //! style string is used to color the elapsed part, the alternative //! style is used for the bar that is yet to render. //! * `wide_bar`: like `bar` but always fills the remaining space. It should not be used with //! `wide_msg`. //! * `spinner`: renders the spinner (current tick string). //! * `prefix`: renders the prefix set on the progress bar. //! * `msg`: renders the currently set message on the progress bar. //! * `wide_msg`: like `msg` but always fills the remaining space and truncates. It should not be used //! with `wide_bar`. //! * `pos`: renders the current position of the bar as integer //! * `human_pos`: renders the current position of the bar as an integer, with commas as the //! thousands separator. //! * `len`: renders the amount of work to be done as an integer //! * `human_len`: renders the total length of the bar as an integer, with commas as the thousands //! separator. //! * `bytes`: renders the current position of the bar as bytes. //! * `percent`: renders the current position of the bar as a percentage of the total length. //! * `total_bytes`: renders the total length of the bar as bytes. //! * `elapsed_precise`: renders the elapsed time as `HH:MM:SS`. //! * `elapsed`: renders the elapsed time as `42s`, `1m` etc. //! * `per_sec`: renders the speed in steps per second. //! * `bytes_per_sec`: renders the speed in bytes per second. //! * `binary_bytes_per_sec`: renders the speed in bytes per second using //! power-of-two units, i.e. `MiB`, `KiB`, etc. //! * `eta_precise`: the remaining time (like `elapsed_precise`). //! * `eta`: the remaining time (like `elapsed`). //! * `duration_precise`: the extrapolated total duration (like `elapsed_precise`). //! * `duration`: the extrapolated total duration time (like `elapsed`). //! //! The design of the progress bar can be altered with the integrated //! template functionality. The template can be set by changing a //! `ProgressStyle` and attaching it to the progress bar. //! //! # Human Readable Formatting //! //! There are some formatting wrappers for showing elapsed time and //! file sizes for human users: //! //! ```rust //! # use std::time::Duration; //! use indicatif::{HumanBytes, HumanCount, HumanDuration, HumanFloatCount}; //! //! assert_eq!("3.00 MiB", HumanBytes(3*1024*1024).to_string()); //! assert_eq!("8 seconds", HumanDuration(Duration::from_secs(8)).to_string()); //! assert_eq!("33,857,009", HumanCount(33857009).to_string()); //! assert_eq!("33,857,009.1235", HumanFloatCount(33857009.123456).to_string()); //! ``` //! //! # Feature Flags //! //! * `rayon`: adds rayon support //! * `improved_unicode`: adds improved unicode support (graphemes, better width calculation) #![cfg_attr(docsrs, feature(doc_cfg))] #![warn(unreachable_pub)] mod draw_target; mod format; #[cfg(feature = "in_memory")] mod in_memory; mod iter; mod multi; mod progress_bar; #[cfg(feature = "rayon")] mod rayon; mod state; pub mod style; mod term_like; pub use crate::draw_target::ProgressDrawTarget; pub use crate::format::{ BinaryBytes, DecimalBytes, FormattedDuration, HumanBytes, HumanCount, HumanDuration, HumanFloatCount, }; #[cfg(feature = "in_memory")] pub use crate::in_memory::InMemoryTerm; pub use crate::iter::{ProgressBarIter, ProgressIterator}; pub use crate::multi::{MultiProgress, MultiProgressAlignment}; pub use crate::progress_bar::{ProgressBar, WeakProgressBar}; #[cfg(feature = "rayon")] pub use crate::rayon::ParallelProgressIterator; pub use crate::state::{ProgressFinish, ProgressState}; pub use crate::style::ProgressStyle; pub use crate::term_like::TermLike; indicatif-0.17.7/src/multi.rs000064400000000000000000000572531046102023000141550ustar 00000000000000use std::fmt::{Debug, Formatter}; use std::io; use std::sync::{Arc, RwLock}; use std::thread::panicking; #[cfg(not(target_arch = "wasm32"))] use std::time::Instant; use crate::draw_target::{DrawState, DrawStateWrapper, LineAdjust, ProgressDrawTarget}; use crate::progress_bar::ProgressBar; #[cfg(target_arch = "wasm32")] use instant::Instant; /// Manages multiple progress bars from different threads #[derive(Debug, Clone)] pub struct MultiProgress { pub(crate) state: Arc>, } impl Default for MultiProgress { fn default() -> Self { Self::with_draw_target(ProgressDrawTarget::stderr()) } } impl MultiProgress { /// Creates a new multi progress object. /// /// Progress bars added to this object by default draw directly to stderr, and refresh /// a maximum of 15 times a second. To change the refresh rate set the draw target to /// one with a different refresh rate. pub fn new() -> Self { Self::default() } /// Creates a new multi progress object with the given draw target. pub fn with_draw_target(draw_target: ProgressDrawTarget) -> Self { Self { state: Arc::new(RwLock::new(MultiState::new(draw_target))), } } /// Sets a different draw target for the multiprogress bar. pub fn set_draw_target(&self, target: ProgressDrawTarget) { let mut state = self.state.write().unwrap(); state.draw_target.disconnect(Instant::now()); state.draw_target = target; } /// Set whether we should try to move the cursor when possible instead of clearing lines. /// /// This can reduce flickering, but do not enable it if you intend to change the number of /// progress bars. pub fn set_move_cursor(&self, move_cursor: bool) { self.state.write().unwrap().move_cursor = move_cursor; } /// Set alignment flag pub fn set_alignment(&self, alignment: MultiProgressAlignment) { self.state.write().unwrap().alignment = alignment; } /// Adds a progress bar. /// /// The progress bar added will have the draw target changed to a /// remote draw target that is intercepted by the multi progress /// object overriding custom `ProgressDrawTarget` settings. /// /// Adding a progress bar that is already a member of the `MultiProgress` /// will have no effect. pub fn add(&self, pb: ProgressBar) -> ProgressBar { self.internalize(InsertLocation::End, pb) } /// Inserts a progress bar. /// /// The progress bar inserted at position `index` will have the draw /// target changed to a remote draw target that is intercepted by the /// multi progress object overriding custom `ProgressDrawTarget` settings. /// /// If `index >= MultiProgressState::objects.len()`, the progress bar /// is added to the end of the list. /// /// Inserting a progress bar that is already a member of the `MultiProgress` /// will have no effect. pub fn insert(&self, index: usize, pb: ProgressBar) -> ProgressBar { self.internalize(InsertLocation::Index(index), pb) } /// Inserts a progress bar from the back. /// /// The progress bar inserted at position `MultiProgressState::objects.len() - index` /// will have the draw target changed to a remote draw target that is /// intercepted by the multi progress object overriding custom /// `ProgressDrawTarget` settings. /// /// If `index >= MultiProgressState::objects.len()`, the progress bar /// is added to the start of the list. /// /// Inserting a progress bar that is already a member of the `MultiProgress` /// will have no effect. pub fn insert_from_back(&self, index: usize, pb: ProgressBar) -> ProgressBar { self.internalize(InsertLocation::IndexFromBack(index), pb) } /// Inserts a progress bar before an existing one. /// /// The progress bar added will have the draw target changed to a /// remote draw target that is intercepted by the multi progress /// object overriding custom `ProgressDrawTarget` settings. /// /// Inserting a progress bar that is already a member of the `MultiProgress` /// will have no effect. pub fn insert_before(&self, before: &ProgressBar, pb: ProgressBar) -> ProgressBar { self.internalize(InsertLocation::Before(before.index().unwrap()), pb) } /// Inserts a progress bar after an existing one. /// /// The progress bar added will have the draw target changed to a /// remote draw target that is intercepted by the multi progress /// object overriding custom `ProgressDrawTarget` settings. /// /// Inserting a progress bar that is already a member of the `MultiProgress` /// will have no effect. pub fn insert_after(&self, after: &ProgressBar, pb: ProgressBar) -> ProgressBar { self.internalize(InsertLocation::After(after.index().unwrap()), pb) } /// Removes a progress bar. /// /// The progress bar is removed only if it was previously inserted or added /// by the methods `MultiProgress::insert` or `MultiProgress::add`. /// If the passed progress bar does not satisfy the condition above, /// the `remove` method does nothing. pub fn remove(&self, pb: &ProgressBar) { let mut state = pb.state(); let idx = match &state.draw_target.remote() { Some((state, idx)) => { // Check that this progress bar is owned by the current MultiProgress. assert!(Arc::ptr_eq(&self.state, state)); *idx } _ => return, }; state.draw_target = ProgressDrawTarget::hidden(); self.state.write().unwrap().remove_idx(idx); } fn internalize(&self, location: InsertLocation, pb: ProgressBar) -> ProgressBar { let mut state = self.state.write().unwrap(); let idx = state.insert(location); drop(state); pb.set_draw_target(ProgressDrawTarget::new_remote(self.state.clone(), idx)); pb } /// Print a log line above all progress bars in the [`MultiProgress`] /// /// If the draw target is hidden (e.g. when standard output is not a terminal), `println()` /// will not do anything. pub fn println>(&self, msg: I) -> io::Result<()> { let mut state = self.state.write().unwrap(); state.println(msg, Instant::now()) } /// Hide all progress bars temporarily, execute `f`, then redraw the [`MultiProgress`] /// /// Executes 'f' even if the draw target is hidden. /// /// Useful for external code that writes to the standard output. /// /// **Note:** The internal lock is held while `f` is executed. Other threads trying to print /// anything on the progress bar will be blocked until `f` finishes. /// Therefore, it is recommended to avoid long-running operations in `f`. pub fn suspend R, R>(&self, f: F) -> R { let mut state = self.state.write().unwrap(); state.suspend(f, Instant::now()) } pub fn clear(&self) -> io::Result<()> { self.state.write().unwrap().clear(Instant::now()) } pub fn is_hidden(&self) -> bool { self.state.read().unwrap().is_hidden() } } #[derive(Debug)] pub(crate) struct MultiState { /// The collection of states corresponding to progress bars members: Vec, /// Set of removed bars, should have corresponding members in the `members` vector with a /// `draw_state` of `None`. free_set: Vec, /// Indices to the `draw_states` to maintain correct visual order ordering: Vec, /// Target for draw operation for MultiProgress draw_target: ProgressDrawTarget, /// Whether or not to just move cursor instead of clearing lines move_cursor: bool, /// Controls how the multi progress is aligned if some of its progress bars get removed, default is `Top` alignment: MultiProgressAlignment, /// Lines to be drawn above everything else in the MultiProgress. These specifically come from /// calling `ProgressBar::println` on a pb that is connected to a `MultiProgress`. orphan_lines: Vec, /// The count of currently visible zombie lines. zombie_lines_count: usize, } impl MultiState { fn new(draw_target: ProgressDrawTarget) -> Self { Self { members: vec![], free_set: vec![], ordering: vec![], draw_target, move_cursor: false, alignment: MultiProgressAlignment::default(), orphan_lines: Vec::new(), zombie_lines_count: 0, } } pub(crate) fn mark_zombie(&mut self, index: usize) { let member = &mut self.members[index]; // If the zombie is the first visual bar then we can reap it right now instead of // deferring it to the next draw. if index != self.ordering.first().copied().unwrap() { member.is_zombie = true; return; } let line_count = member .draw_state .as_ref() .map(|d| d.lines.len()) .unwrap_or_default(); // Track the total number of zombie lines on the screen self.zombie_lines_count = self.zombie_lines_count.saturating_add(line_count); // Make `DrawTarget` forget about the zombie lines so that they aren't cleared on next draw. self.draw_target .adjust_last_line_count(LineAdjust::Keep(line_count)); self.remove_idx(index); } pub(crate) fn draw( &mut self, mut force_draw: bool, extra_lines: Option>, now: Instant, ) -> io::Result<()> { if panicking() { return Ok(()); } let width = self.width() as f64; // Calculate real length based on terminal width // This take in account linewrap from terminal fn real_len(lines: &[String], width: f64) -> usize { lines.iter().fold(0, |sum, val| { sum + (console::measure_text_width(val) as f64 / width).ceil() as usize }) } // Assumption: if extra_lines is not None, then it has at least one line debug_assert_eq!( extra_lines.is_some(), extra_lines.as_ref().map(Vec::len).unwrap_or_default() > 0 ); let mut reap_indices = vec![]; // Reap all consecutive 'zombie' progress bars from head of the list. let mut adjust = 0; for &index in &self.ordering { let member = &self.members[index]; if !member.is_zombie { break; } let line_count = member .draw_state .as_ref() .map(|d| real_len(&d.lines, width)) .unwrap_or_default(); // Track the total number of zombie lines on the screen. self.zombie_lines_count += line_count; // Track the number of zombie lines that will be drawn by this call to draw. adjust += line_count; reap_indices.push(index); } // If this draw is due to a `println`, then we need to erase all the zombie lines. // This is because `println` is supposed to appear above all other elements in the // `MultiProgress`. if extra_lines.is_some() { self.draw_target .adjust_last_line_count(LineAdjust::Clear(self.zombie_lines_count)); self.zombie_lines_count = 0; } let orphan_lines_count = real_len(&self.orphan_lines, width); force_draw |= orphan_lines_count > 0; let mut drawable = match self.draw_target.drawable(force_draw, now) { Some(drawable) => drawable, None => return Ok(()), }; let mut draw_state = drawable.state(); draw_state.orphan_lines_count = orphan_lines_count; draw_state.alignment = self.alignment; if let Some(extra_lines) = &extra_lines { draw_state.lines.extend_from_slice(extra_lines.as_slice()); draw_state.orphan_lines_count += real_len(extra_lines, width); } // Add lines from `ProgressBar::println` call. draw_state.lines.append(&mut self.orphan_lines); for index in &self.ordering { let member = &self.members[*index]; if let Some(state) = &member.draw_state { draw_state.lines.extend_from_slice(&state.lines[..]); } } drop(draw_state); let drawable = drawable.draw(); for index in reap_indices { self.remove_idx(index); } // The zombie lines were drawn for the last time, so make `DrawTarget` forget about them // so they aren't cleared on next draw. if extra_lines.is_none() { self.draw_target .adjust_last_line_count(LineAdjust::Keep(adjust)); } drawable } pub(crate) fn println>(&mut self, msg: I, now: Instant) -> io::Result<()> { let msg = msg.as_ref(); // If msg is "", make sure a line is still printed let lines: Vec = match msg.is_empty() { false => msg.lines().map(Into::into).collect(), true => vec![String::new()], }; self.draw(true, Some(lines), now) } pub(crate) fn draw_state(&mut self, idx: usize) -> DrawStateWrapper<'_> { let member = self.members.get_mut(idx).unwrap(); // alignment is handled by the `MultiProgress`'s underlying draw target, so there is no // point in propagating it here. let state = member.draw_state.get_or_insert(DrawState { move_cursor: self.move_cursor, ..Default::default() }); DrawStateWrapper::for_multi(state, &mut self.orphan_lines) } pub(crate) fn is_hidden(&self) -> bool { self.draw_target.is_hidden() } pub(crate) fn suspend R, R>(&mut self, f: F, now: Instant) -> R { self.clear(now).unwrap(); let ret = f(); self.draw(true, None, Instant::now()).unwrap(); ret } pub(crate) fn width(&self) -> u16 { self.draw_target.width() } fn insert(&mut self, location: InsertLocation) -> usize { let idx = if let Some(idx) = self.free_set.pop() { self.members[idx] = MultiStateMember::default(); idx } else { self.members.push(MultiStateMember::default()); self.members.len() - 1 }; match location { InsertLocation::End => self.ordering.push(idx), InsertLocation::Index(pos) => { let pos = Ord::min(pos, self.ordering.len()); self.ordering.insert(pos, idx); } InsertLocation::IndexFromBack(pos) => { let pos = self.ordering.len().saturating_sub(pos); self.ordering.insert(pos, idx); } InsertLocation::After(after_idx) => { let pos = self.ordering.iter().position(|i| *i == after_idx).unwrap(); self.ordering.insert(pos + 1, idx); } InsertLocation::Before(before_idx) => { let pos = self.ordering.iter().position(|i| *i == before_idx).unwrap(); self.ordering.insert(pos, idx); } } assert_eq!( self.len(), self.ordering.len(), "Draw state is inconsistent" ); idx } fn clear(&mut self, now: Instant) -> io::Result<()> { match self.draw_target.drawable(true, now) { Some(mut drawable) => { // Make the clear operation also wipe out zombie lines drawable.adjust_last_line_count(LineAdjust::Clear(self.zombie_lines_count)); self.zombie_lines_count = 0; drawable.clear() } None => Ok(()), } } fn remove_idx(&mut self, idx: usize) { if self.free_set.contains(&idx) { return; } self.members[idx] = MultiStateMember::default(); self.free_set.push(idx); self.ordering.retain(|&x| x != idx); assert_eq!( self.len(), self.ordering.len(), "Draw state is inconsistent" ); } fn len(&self) -> usize { self.members.len() - self.free_set.len() } } #[derive(Default)] struct MultiStateMember { /// Draw state will be `None` for members that haven't been drawn before, or for entries that /// correspond to something in the free set. draw_state: Option, /// Whether the corresponding progress bar (more precisely, `BarState`) has been dropped. is_zombie: bool, } impl Debug for MultiStateMember { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("MultiStateElement") .field("draw_state", &self.draw_state) .field("is_zombie", &self.is_zombie) .finish_non_exhaustive() } } /// Vertical alignment of a multi progress. /// /// The alignment controls how the multi progress is aligned if some of its progress bars get removed. /// E.g. `Top` alignment (default), when _progress bar 2_ is removed: /// ```ignore /// [0/100] progress bar 1 [0/100] progress bar 1 /// [0/100] progress bar 2 => [0/100] progress bar 3 /// [0/100] progress bar 3 /// ``` /// /// `Bottom` alignment /// ```ignore /// [0/100] progress bar 1 /// [0/100] progress bar 2 => [0/100] progress bar 1 /// [0/100] progress bar 3 [0/100] progress bar 3 /// ``` #[derive(Debug, Copy, Clone)] pub enum MultiProgressAlignment { Top, Bottom, } impl Default for MultiProgressAlignment { fn default() -> Self { Self::Top } } enum InsertLocation { End, Index(usize), IndexFromBack(usize), After(usize), Before(usize), } #[cfg(test)] mod tests { use crate::{MultiProgress, ProgressBar, ProgressDrawTarget}; #[test] fn late_pb_drop() { let pb = ProgressBar::new(10); let mpb = MultiProgress::new(); // This clone call is required to trigger a now fixed bug. // See for context #[allow(clippy::redundant_clone)] mpb.add(pb.clone()); } #[test] fn progress_bar_sync_send() { let _: Box = Box::new(ProgressBar::new(1)); let _: Box = Box::new(ProgressBar::new(1)); let _: Box = Box::new(MultiProgress::new()); let _: Box = Box::new(MultiProgress::new()); } #[test] fn multi_progress_hidden() { let mpb = MultiProgress::with_draw_target(ProgressDrawTarget::hidden()); let pb = mpb.add(ProgressBar::new(123)); pb.finish(); } #[test] fn multi_progress_modifications() { let mp = MultiProgress::new(); let p0 = mp.add(ProgressBar::new(1)); let p1 = mp.add(ProgressBar::new(1)); let p2 = mp.add(ProgressBar::new(1)); let p3 = mp.add(ProgressBar::new(1)); mp.remove(&p2); mp.remove(&p1); let p4 = mp.insert(1, ProgressBar::new(1)); let state = mp.state.read().unwrap(); // the removed place for p1 is reused assert_eq!(state.members.len(), 4); assert_eq!(state.len(), 3); // free_set may contain 1 or 2 match state.free_set.last() { Some(1) => { assert_eq!(state.ordering, vec![0, 2, 3]); assert!(state.members[1].draw_state.is_none()); assert_eq!(p4.index().unwrap(), 2); } Some(2) => { assert_eq!(state.ordering, vec![0, 1, 3]); assert!(state.members[2].draw_state.is_none()); assert_eq!(p4.index().unwrap(), 1); } _ => unreachable!(), } assert_eq!(p0.index().unwrap(), 0); assert_eq!(p1.index(), None); assert_eq!(p2.index(), None); assert_eq!(p3.index().unwrap(), 3); } #[test] fn multi_progress_insert_from_back() { let mp = MultiProgress::new(); let p0 = mp.add(ProgressBar::new(1)); let p1 = mp.add(ProgressBar::new(1)); let p2 = mp.add(ProgressBar::new(1)); let p3 = mp.insert_from_back(1, ProgressBar::new(1)); let p4 = mp.insert_from_back(10, ProgressBar::new(1)); let state = mp.state.read().unwrap(); assert_eq!(state.ordering, vec![4, 0, 1, 3, 2]); assert_eq!(p0.index().unwrap(), 0); assert_eq!(p1.index().unwrap(), 1); assert_eq!(p2.index().unwrap(), 2); assert_eq!(p3.index().unwrap(), 3); assert_eq!(p4.index().unwrap(), 4); } #[test] fn multi_progress_insert_after() { let mp = MultiProgress::new(); let p0 = mp.add(ProgressBar::new(1)); let p1 = mp.add(ProgressBar::new(1)); let p2 = mp.add(ProgressBar::new(1)); let p3 = mp.insert_after(&p2, ProgressBar::new(1)); let p4 = mp.insert_after(&p0, ProgressBar::new(1)); let state = mp.state.read().unwrap(); assert_eq!(state.ordering, vec![0, 4, 1, 2, 3]); assert_eq!(p0.index().unwrap(), 0); assert_eq!(p1.index().unwrap(), 1); assert_eq!(p2.index().unwrap(), 2); assert_eq!(p3.index().unwrap(), 3); assert_eq!(p4.index().unwrap(), 4); } #[test] fn multi_progress_insert_before() { let mp = MultiProgress::new(); let p0 = mp.add(ProgressBar::new(1)); let p1 = mp.add(ProgressBar::new(1)); let p2 = mp.add(ProgressBar::new(1)); let p3 = mp.insert_before(&p0, ProgressBar::new(1)); let p4 = mp.insert_before(&p2, ProgressBar::new(1)); let state = mp.state.read().unwrap(); assert_eq!(state.ordering, vec![3, 0, 1, 4, 2]); assert_eq!(p0.index().unwrap(), 0); assert_eq!(p1.index().unwrap(), 1); assert_eq!(p2.index().unwrap(), 2); assert_eq!(p3.index().unwrap(), 3); assert_eq!(p4.index().unwrap(), 4); } #[test] fn multi_progress_insert_before_and_after() { let mp = MultiProgress::new(); let p0 = mp.add(ProgressBar::new(1)); let p1 = mp.add(ProgressBar::new(1)); let p2 = mp.add(ProgressBar::new(1)); let p3 = mp.insert_before(&p0, ProgressBar::new(1)); let p4 = mp.insert_after(&p3, ProgressBar::new(1)); let p5 = mp.insert_after(&p3, ProgressBar::new(1)); let p6 = mp.insert_before(&p1, ProgressBar::new(1)); let state = mp.state.read().unwrap(); assert_eq!(state.ordering, vec![3, 5, 4, 0, 6, 1, 2]); assert_eq!(p0.index().unwrap(), 0); assert_eq!(p1.index().unwrap(), 1); assert_eq!(p2.index().unwrap(), 2); assert_eq!(p3.index().unwrap(), 3); assert_eq!(p4.index().unwrap(), 4); assert_eq!(p5.index().unwrap(), 5); assert_eq!(p6.index().unwrap(), 6); } #[test] fn multi_progress_multiple_remove() { let mp = MultiProgress::new(); let p0 = mp.add(ProgressBar::new(1)); let p1 = mp.add(ProgressBar::new(1)); // double remove beyond the first one have no effect mp.remove(&p0); mp.remove(&p0); mp.remove(&p0); let state = mp.state.read().unwrap(); // the removed place for p1 is reused assert_eq!(state.members.len(), 2); assert_eq!(state.free_set.len(), 1); assert_eq!(state.len(), 1); assert!(state.members[0].draw_state.is_none()); assert_eq!(state.free_set.last(), Some(&0)); assert_eq!(state.ordering, vec![1]); assert_eq!(p0.index(), None); assert_eq!(p1.index().unwrap(), 1); } #[test] fn mp_no_crash_double_add() { let mp = MultiProgress::new(); let pb = mp.add(ProgressBar::new(10)); mp.add(pb); } } indicatif-0.17.7/src/progress_bar.rs000064400000000000000000000642771046102023000155170ustar 00000000000000#[cfg(test)] use portable_atomic::{AtomicBool, Ordering}; use std::borrow::Cow; use std::sync::{Arc, Condvar, Mutex, MutexGuard, Weak}; use std::time::Duration; #[cfg(not(target_arch = "wasm32"))] use std::time::Instant; use std::{fmt, io, thread}; #[cfg(target_arch = "wasm32")] use instant::Instant; #[cfg(test)] use once_cell::sync::Lazy; use crate::draw_target::ProgressDrawTarget; use crate::state::{AtomicPosition, BarState, ProgressFinish, Reset, TabExpandedString}; use crate::style::ProgressStyle; use crate::{ProgressBarIter, ProgressIterator, ProgressState}; /// A progress bar or spinner /// /// The progress bar is an [`Arc`] around its internal state. When the progress bar is cloned it /// just increments the refcount (so the original and its clone share the same state). #[derive(Clone)] pub struct ProgressBar { state: Arc>, pos: Arc, ticker: Arc>>, } impl fmt::Debug for ProgressBar { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ProgressBar").finish() } } impl ProgressBar { /// Creates a new progress bar with a given length /// /// This progress bar by default draws directly to stderr, and refreshes a maximum of 15 times /// a second. To change the refresh rate, set the draw target to one with a different refresh /// rate. pub fn new(len: u64) -> Self { Self::with_draw_target(Some(len), ProgressDrawTarget::stderr()) } /// Creates a completely hidden progress bar /// /// This progress bar still responds to API changes but it does not have a length or render in /// any way. pub fn hidden() -> Self { Self::with_draw_target(None, ProgressDrawTarget::hidden()) } /// Creates a new progress bar with a given length and draw target pub fn with_draw_target(len: Option, draw_target: ProgressDrawTarget) -> Self { let pos = Arc::new(AtomicPosition::new()); Self { state: Arc::new(Mutex::new(BarState::new(len, draw_target, pos.clone()))), pos, ticker: Arc::new(Mutex::new(None)), } } /// Get a clone of the current progress bar style. pub fn style(&self) -> ProgressStyle { self.state().style.clone() } /// A convenience builder-like function for a progress bar with a given style pub fn with_style(self, style: ProgressStyle) -> Self { self.set_style(style); self } /// A convenience builder-like function for a progress bar with a given tab width pub fn with_tab_width(self, tab_width: usize) -> Self { self.state().set_tab_width(tab_width); self } /// A convenience builder-like function for a progress bar with a given prefix /// /// For the prefix to be visible, the `{prefix}` placeholder must be present in the template /// (see [`ProgressStyle`]). pub fn with_prefix(self, prefix: impl Into>) -> Self { let mut state = self.state(); state.state.prefix = TabExpandedString::new(prefix.into(), state.tab_width); drop(state); self } /// A convenience builder-like function for a progress bar with a given message /// /// For the message to be visible, the `{msg}` placeholder must be present in the template (see /// [`ProgressStyle`]). pub fn with_message(self, message: impl Into>) -> Self { let mut state = self.state(); state.state.message = TabExpandedString::new(message.into(), state.tab_width); drop(state); self } /// A convenience builder-like function for a progress bar with a given position pub fn with_position(self, pos: u64) -> Self { self.state().state.set_pos(pos); self } /// A convenience builder-like function for a progress bar with a given elapsed time pub fn with_elapsed(self, elapsed: Duration) -> Self { self.state().state.started = Instant::now().checked_sub(elapsed).unwrap(); self } /// Sets the finish behavior for the progress bar /// /// This behavior is invoked when [`ProgressBar`] or /// [`ProgressBarIter`] completes and /// [`ProgressBar::is_finished()`] is false. /// If you don't want the progress bar to be automatically finished then /// call `on_finish(None)`. /// /// [`ProgressBar`]: crate::ProgressBar /// [`ProgressBarIter`]: crate::ProgressBarIter /// [`ProgressBar::is_finished()`]: crate::ProgressBar::is_finished pub fn with_finish(self, finish: ProgressFinish) -> Self { self.state().on_finish = finish; self } /// Creates a new spinner /// /// This spinner by default draws directly to stderr. This adds the default spinner style to it. pub fn new_spinner() -> Self { let rv = Self::with_draw_target(None, ProgressDrawTarget::stderr()); rv.set_style(ProgressStyle::default_spinner()); rv } /// Overrides the stored style /// /// This does not redraw the bar. Call [`ProgressBar::tick()`] to force it. pub fn set_style(&self, style: ProgressStyle) { self.state().set_style(style); } /// Sets the tab width (default: 8). All tabs will be expanded to this many spaces. pub fn set_tab_width(&mut self, tab_width: usize) { let mut state = self.state(); state.set_tab_width(tab_width); state.draw(true, Instant::now()).unwrap(); } /// Spawns a background thread to tick the progress bar /// /// When this is enabled a background thread will regularly tick the progress bar in the given /// interval. This is useful to advance progress bars that are very slow by themselves. /// /// When steady ticks are enabled, calling [`ProgressBar::tick()`] on a progress bar does not /// have any effect. pub fn enable_steady_tick(&self, interval: Duration) { // The way we test for ticker termination is with a single static `AtomicBool`. Since cargo // runs tests concurrently, we have a `TICKER_TEST` lock to make sure tests using ticker // don't step on each other. This check catches attempts to use tickers in tests without // acquiring the lock. #[cfg(test)] { let guard = TICKER_TEST.try_lock(); let lock_acquired = guard.is_ok(); // Drop the guard before panicking to avoid poisoning the lock (which would cause other // ticker tests to fail) drop(guard); if lock_acquired { panic!("you must acquire the TICKER_TEST lock in your test to use this method"); } } if interval.is_zero() { return; } self.stop_and_replace_ticker(Some(interval)); } /// Undoes [`ProgressBar::enable_steady_tick()`] pub fn disable_steady_tick(&self) { self.stop_and_replace_ticker(None); } fn stop_and_replace_ticker(&self, interval: Option) { let mut ticker_state = self.ticker.lock().unwrap(); if let Some(ticker) = ticker_state.take() { ticker.stop(); } *ticker_state = interval.map(|interval| Ticker::new(interval, &self.state)); } /// Manually ticks the spinner or progress bar /// /// This automatically happens on any other change to a progress bar. pub fn tick(&self) { self.tick_inner(Instant::now()); } fn tick_inner(&self, now: Instant) { // Only tick if a `Ticker` isn't installed if self.ticker.lock().unwrap().is_none() { self.state().tick(now); } } /// Advances the position of the progress bar by `delta` pub fn inc(&self, delta: u64) { self.pos.inc(delta); let now = Instant::now(); if self.pos.allow(now) { self.tick_inner(now); } } /// A quick convenience check if the progress bar is hidden pub fn is_hidden(&self) -> bool { self.state().draw_target.is_hidden() } /// Indicates that the progress bar finished pub fn is_finished(&self) -> bool { self.state().state.is_finished() } /// Print a log line above the progress bar /// /// If the progress bar is hidden (e.g. when standard output is not a terminal), `println()` /// will not do anything. If you want to write to the standard output in such cases as well, use /// [`suspend`] instead. /// /// If the progress bar was added to a [`MultiProgress`], the log line will be /// printed above all other progress bars. /// /// [`suspend`]: ProgressBar::suspend /// [`MultiProgress`]: crate::MultiProgress pub fn println>(&self, msg: I) { self.state().println(Instant::now(), msg.as_ref()); } /// Update the `ProgressBar`'s inner [`ProgressState`] pub fn update(&self, f: impl FnOnce(&mut ProgressState)) { self.state() .update(Instant::now(), f, self.ticker.lock().unwrap().is_none()); } /// Sets the position of the progress bar pub fn set_position(&self, pos: u64) { self.pos.set(pos); let now = Instant::now(); if self.pos.allow(now) { self.tick_inner(now); } } /// Sets the length of the progress bar pub fn set_length(&self, len: u64) { self.state().set_length(Instant::now(), len); } /// Increase the length of the progress bar pub fn inc_length(&self, delta: u64) { self.state().inc_length(Instant::now(), delta); } /// Sets the current prefix of the progress bar /// /// For the prefix to be visible, the `{prefix}` placeholder must be present in the template /// (see [`ProgressStyle`]). pub fn set_prefix(&self, prefix: impl Into>) { let mut state = self.state(); state.state.prefix = TabExpandedString::new(prefix.into(), state.tab_width); state.update_estimate_and_draw(Instant::now()); } /// Sets the current message of the progress bar /// /// For the message to be visible, the `{msg}` placeholder must be present in the template (see /// [`ProgressStyle`]). pub fn set_message(&self, msg: impl Into>) { let mut state = self.state(); state.state.message = TabExpandedString::new(msg.into(), state.tab_width); state.update_estimate_and_draw(Instant::now()); } /// Creates a new weak reference to this `ProgressBar` pub fn downgrade(&self) -> WeakProgressBar { WeakProgressBar { state: Arc::downgrade(&self.state), pos: Arc::downgrade(&self.pos), ticker: Arc::downgrade(&self.ticker), } } /// Resets the ETA calculation /// /// This can be useful if the progress bars made a large jump or was paused for a prolonged /// time. pub fn reset_eta(&self) { self.state().reset(Instant::now(), Reset::Eta); } /// Resets elapsed time and the ETA calculation pub fn reset_elapsed(&self) { self.state().reset(Instant::now(), Reset::Elapsed); } /// Resets all of the progress bar state pub fn reset(&self) { self.state().reset(Instant::now(), Reset::All); } /// Finishes the progress bar and leaves the current message pub fn finish(&self) { self.state() .finish_using_style(Instant::now(), ProgressFinish::AndLeave); } /// Finishes the progress bar and sets a message /// /// For the message to be visible, the `{msg}` placeholder must be present in the template (see /// [`ProgressStyle`]). pub fn finish_with_message(&self, msg: impl Into>) { self.state() .finish_using_style(Instant::now(), ProgressFinish::WithMessage(msg.into())); } /// Finishes the progress bar and completely clears it pub fn finish_and_clear(&self) { self.state() .finish_using_style(Instant::now(), ProgressFinish::AndClear); } /// Finishes the progress bar and leaves the current message and progress pub fn abandon(&self) { self.state() .finish_using_style(Instant::now(), ProgressFinish::Abandon); } /// Finishes the progress bar and sets a message, and leaves the current progress /// /// For the message to be visible, the `{msg}` placeholder must be present in the template (see /// [`ProgressStyle`]). pub fn abandon_with_message(&self, msg: impl Into>) { self.state().finish_using_style( Instant::now(), ProgressFinish::AbandonWithMessage(msg.into()), ); } /// Finishes the progress bar using the behavior stored in the [`ProgressStyle`] /// /// See [`ProgressBar::with_finish()`]. pub fn finish_using_style(&self) { let mut state = self.state(); let finish = state.on_finish.clone(); state.finish_using_style(Instant::now(), finish); } /// Sets a different draw target for the progress bar /// /// This can be used to draw the progress bar to stderr (this is the default): /// /// ```rust,no_run /// # use indicatif::{ProgressBar, ProgressDrawTarget}; /// let pb = ProgressBar::new(100); /// pb.set_draw_target(ProgressDrawTarget::stderr()); /// ``` /// /// **Note:** Calling this method on a [`ProgressBar`] linked with a [`MultiProgress`] (after /// running [`MultiProgress::add`]) will unlink this progress bar. If you don't want this /// behavior, call [`MultiProgress::set_draw_target`] instead. /// /// [`MultiProgress`]: crate::MultiProgress /// [`MultiProgress::add`]: crate::MultiProgress::add /// [`MultiProgress::set_draw_target`]: crate::MultiProgress::set_draw_target pub fn set_draw_target(&self, target: ProgressDrawTarget) { let mut state = self.state(); state.draw_target.disconnect(Instant::now()); state.draw_target = target; } /// Hide the progress bar temporarily, execute `f`, then redraw the progress bar /// /// Useful for external code that writes to the standard output. /// /// If the progress bar was added to a MultiProgress, it will suspend the entire MultiProgress /// /// **Note:** The internal lock is held while `f` is executed. Other threads trying to print /// anything on the progress bar will be blocked until `f` finishes. /// Therefore, it is recommended to avoid long-running operations in `f`. /// /// ```rust,no_run /// # use indicatif::ProgressBar; /// let mut pb = ProgressBar::new(3); /// pb.suspend(|| { /// println!("Log message"); /// }) /// ``` pub fn suspend R, R>(&self, f: F) -> R { self.state().suspend(Instant::now(), f) } /// Wraps an [`Iterator`] with the progress bar /// /// ```rust,no_run /// # use indicatif::ProgressBar; /// let v = vec![1, 2, 3]; /// let pb = ProgressBar::new(3); /// for item in pb.wrap_iter(v.iter()) { /// // ... /// } /// ``` pub fn wrap_iter(&self, it: It) -> ProgressBarIter { it.progress_with(self.clone()) } /// Wraps an [`io::Read`] with the progress bar /// /// ```rust,no_run /// # use std::fs::File; /// # use std::io; /// # use indicatif::ProgressBar; /// # fn test () -> io::Result<()> { /// let source = File::open("work.txt")?; /// let mut target = File::create("done.txt")?; /// let pb = ProgressBar::new(source.metadata()?.len()); /// io::copy(&mut pb.wrap_read(source), &mut target); /// # Ok(()) /// # } /// ``` pub fn wrap_read(&self, read: R) -> ProgressBarIter { ProgressBarIter { progress: self.clone(), it: read, } } /// Wraps an [`io::Write`] with the progress bar /// /// ```rust,no_run /// # use std::fs::File; /// # use std::io; /// # use indicatif::ProgressBar; /// # fn test () -> io::Result<()> { /// let mut source = File::open("work.txt")?; /// let target = File::create("done.txt")?; /// let pb = ProgressBar::new(source.metadata()?.len()); /// io::copy(&mut source, &mut pb.wrap_write(target)); /// # Ok(()) /// # } /// ``` pub fn wrap_write(&self, write: W) -> ProgressBarIter { ProgressBarIter { progress: self.clone(), it: write, } } #[cfg(feature = "tokio")] #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] /// Wraps an [`tokio::io::AsyncWrite`] with the progress bar /// /// ```rust,no_run /// # use tokio::fs::File; /// # use tokio::io; /// # use indicatif::ProgressBar; /// # async fn test() -> io::Result<()> { /// let mut source = File::open("work.txt").await?; /// let mut target = File::open("done.txt").await?; /// let pb = ProgressBar::new(source.metadata().await?.len()); /// io::copy(&mut source, &mut pb.wrap_async_write(target)).await?; /// # Ok(()) /// # } /// ``` pub fn wrap_async_write( &self, write: W, ) -> ProgressBarIter { ProgressBarIter { progress: self.clone(), it: write, } } #[cfg(feature = "tokio")] #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] /// Wraps an [`tokio::io::AsyncRead`] with the progress bar /// /// ```rust,no_run /// # use tokio::fs::File; /// # use tokio::io; /// # use indicatif::ProgressBar; /// # async fn test() -> io::Result<()> { /// let mut source = File::open("work.txt").await?; /// let mut target = File::open("done.txt").await?; /// let pb = ProgressBar::new(source.metadata().await?.len()); /// io::copy(&mut pb.wrap_async_read(source), &mut target).await?; /// # Ok(()) /// # } /// ``` pub fn wrap_async_read(&self, read: R) -> ProgressBarIter { ProgressBarIter { progress: self.clone(), it: read, } } /// Wraps a [`futures::Stream`](https://docs.rs/futures/0.3/futures/stream/trait.StreamExt.html) with the progress bar /// /// ``` /// # use indicatif::ProgressBar; /// # futures::executor::block_on(async { /// use futures::stream::{self, StreamExt}; /// let pb = ProgressBar::new(10); /// let mut stream = pb.wrap_stream(stream::iter('a'..='z')); /// /// assert_eq!(stream.next().await, Some('a')); /// assert_eq!(stream.count().await, 25); /// # }); // block_on /// ``` #[cfg(feature = "futures")] #[cfg_attr(docsrs, doc(cfg(feature = "futures")))] pub fn wrap_stream(&self, stream: S) -> ProgressBarIter { ProgressBarIter { progress: self.clone(), it: stream, } } /// Returns the current position pub fn position(&self) -> u64 { self.state().state.pos() } /// Returns the current length pub fn length(&self) -> Option { self.state().state.len() } /// Returns the current ETA pub fn eta(&self) -> Duration { self.state().state.eta() } /// Returns the current rate of progress pub fn per_sec(&self) -> f64 { self.state().state.per_sec() } /// Returns the current expected duration pub fn duration(&self) -> Duration { self.state().state.duration() } /// Returns the current elapsed time pub fn elapsed(&self) -> Duration { self.state().state.elapsed() } /// Index in the `MultiState` pub(crate) fn index(&self) -> Option { self.state().draw_target.remote().map(|(_, idx)| idx) } /// Current message pub fn message(&self) -> String { self.state().state.message.expanded().to_string() } /// Current prefix pub fn prefix(&self) -> String { self.state().state.prefix.expanded().to_string() } #[inline] pub(crate) fn state(&self) -> MutexGuard<'_, BarState> { self.state.lock().unwrap() } } /// A weak reference to a `ProgressBar`. /// /// Useful for creating custom steady tick implementations #[derive(Clone, Default)] pub struct WeakProgressBar { state: Weak>, pos: Weak, ticker: Weak>>, } impl WeakProgressBar { /// Create a new `WeakProgressBar` that returns `None` when [`upgrade`] is called. /// /// [`upgrade`]: WeakProgressBar::upgrade pub fn new() -> Self { Self::default() } /// Attempts to upgrade the Weak pointer to a [`ProgressBar`], delaying dropping of the inner /// value if successful. Returns `None` if the inner value has since been dropped. /// /// [`ProgressBar`]: struct.ProgressBar.html pub fn upgrade(&self) -> Option { let state = self.state.upgrade()?; let pos = self.pos.upgrade()?; let ticker = self.ticker.upgrade()?; Some(ProgressBar { state, pos, ticker }) } } pub(crate) struct Ticker { stopping: Arc<(Mutex, Condvar)>, join_handle: Option>, } impl Drop for Ticker { fn drop(&mut self) { self.stop(); self.join_handle.take().map(|handle| handle.join()); } } #[cfg(test)] static TICKER_RUNNING: AtomicBool = AtomicBool::new(false); impl Ticker { pub(crate) fn new(interval: Duration, bar_state: &Arc>) -> Self { debug_assert!(!interval.is_zero()); // A `Mutex` is used as a flag to indicate whether the ticker was requested to stop. // The `Condvar` is used a notification mechanism: when the ticker is dropped, we notify // the thread and interrupt the ticker wait. #[allow(clippy::mutex_atomic)] let stopping = Arc::new((Mutex::new(false), Condvar::new())); let control = TickerControl { stopping: stopping.clone(), state: Arc::downgrade(bar_state), }; let join_handle = thread::spawn(move || control.run(interval)); Self { stopping, join_handle: Some(join_handle), } } pub(crate) fn stop(&self) { *self.stopping.0.lock().unwrap() = true; self.stopping.1.notify_one(); } } struct TickerControl { stopping: Arc<(Mutex, Condvar)>, state: Weak>, } impl TickerControl { fn run(&self, interval: Duration) { #[cfg(test)] TICKER_RUNNING.store(true, Ordering::SeqCst); while let Some(arc) = self.state.upgrade() { let mut state = arc.lock().unwrap(); if state.state.is_finished() { break; } state.tick(Instant::now()); drop(state); // Don't forget to drop the lock before sleeping drop(arc); // Also need to drop Arc otherwise BarState won't be dropped // Wait for `interval` but return early if we are notified to stop let (_, result) = self .stopping .1 .wait_timeout_while(self.stopping.0.lock().unwrap(), interval, |stopped| { !*stopped }) .unwrap(); // If the wait didn't time out, it means we were notified to stop if !result.timed_out() { break; } } #[cfg(test)] TICKER_RUNNING.store(false, Ordering::SeqCst); } } // Tests using the global TICKER_RUNNING flag need to be serialized #[cfg(test)] pub(crate) static TICKER_TEST: Lazy> = Lazy::new(Mutex::default); #[cfg(test)] mod tests { use super::*; #[allow(clippy::float_cmp)] #[test] fn test_pbar_zero() { let pb = ProgressBar::new(0); assert_eq!(pb.state().state.fraction(), 1.0); } #[allow(clippy::float_cmp)] #[test] fn test_pbar_maxu64() { let pb = ProgressBar::new(!0); assert_eq!(pb.state().state.fraction(), 0.0); } #[test] fn test_pbar_overflow() { let pb = ProgressBar::new(1); pb.set_draw_target(ProgressDrawTarget::hidden()); pb.inc(2); pb.finish(); } #[test] fn test_get_position() { let pb = ProgressBar::new(1); pb.set_draw_target(ProgressDrawTarget::hidden()); pb.inc(2); let pos = pb.position(); assert_eq!(pos, 2); } #[test] fn test_weak_pb() { let pb = ProgressBar::new(0); let weak = pb.downgrade(); assert!(weak.upgrade().is_some()); ::std::mem::drop(pb); assert!(weak.upgrade().is_none()); } #[test] fn it_can_wrap_a_reader() { let bytes = &b"I am an implementation of io::Read"[..]; let pb = ProgressBar::new(bytes.len() as u64); let mut reader = pb.wrap_read(bytes); let mut writer = Vec::new(); io::copy(&mut reader, &mut writer).unwrap(); assert_eq!(writer, bytes); } #[test] fn it_can_wrap_a_writer() { let bytes = b"implementation of io::Read"; let mut reader = &bytes[..]; let pb = ProgressBar::new(bytes.len() as u64); let writer = Vec::new(); let mut writer = pb.wrap_write(writer); io::copy(&mut reader, &mut writer).unwrap(); assert_eq!(writer.it, bytes); } #[test] fn ticker_thread_terminates_on_drop() { let _guard = TICKER_TEST.lock().unwrap(); assert!(!TICKER_RUNNING.load(Ordering::SeqCst)); let pb = ProgressBar::new_spinner(); pb.enable_steady_tick(Duration::from_millis(50)); // Give the thread time to start up thread::sleep(Duration::from_millis(250)); assert!(TICKER_RUNNING.load(Ordering::SeqCst)); drop(pb); assert!(!TICKER_RUNNING.load(Ordering::SeqCst)); } #[test] fn ticker_thread_terminates_on_drop_2() { let _guard = TICKER_TEST.lock().unwrap(); assert!(!TICKER_RUNNING.load(Ordering::SeqCst)); let pb = ProgressBar::new_spinner(); pb.enable_steady_tick(Duration::from_millis(50)); let pb2 = pb.clone(); // Give the thread time to start up thread::sleep(Duration::from_millis(250)); assert!(TICKER_RUNNING.load(Ordering::SeqCst)); drop(pb); assert!(TICKER_RUNNING.load(Ordering::SeqCst)); drop(pb2); assert!(!TICKER_RUNNING.load(Ordering::SeqCst)); } } indicatif-0.17.7/src/rayon.rs000064400000000000000000000145011046102023000141400ustar 00000000000000use rayon::iter::plumbing::{Consumer, Folder, Producer, ProducerCallback, UnindexedConsumer}; use rayon::iter::{IndexedParallelIterator, ParallelIterator}; use crate::{ProgressBar, ProgressBarIter}; /// Wraps a Rayon parallel iterator. /// /// See [`ProgressIterator`](trait.ProgressIterator.html) for method /// documentation. #[cfg_attr(docsrs, doc(cfg(feature = "rayon")))] pub trait ParallelProgressIterator where Self: Sized + ParallelIterator, { /// Wrap an iterator with a custom progress bar. fn progress_with(self, progress: ProgressBar) -> ProgressBarIter; /// Wrap an iterator with an explicit element count. fn progress_count(self, len: u64) -> ProgressBarIter { self.progress_with(ProgressBar::new(len)) } fn progress(self) -> ProgressBarIter where Self: IndexedParallelIterator, { let len = u64::try_from(self.len()).unwrap(); self.progress_count(len) } /// Wrap an iterator with a progress bar and style it. fn progress_with_style(self, style: crate::ProgressStyle) -> ProgressBarIter where Self: IndexedParallelIterator, { let len = u64::try_from(self.len()).unwrap(); let bar = ProgressBar::new(len).with_style(style); self.progress_with(bar) } } impl> ParallelProgressIterator for T { fn progress_with(self, progress: ProgressBar) -> ProgressBarIter { ProgressBarIter { it: self, progress } } } impl> IndexedParallelIterator for ProgressBarIter { fn len(&self) -> usize { self.it.len() } fn drive>(self, consumer: C) -> >::Result { let consumer = ProgressConsumer::new(consumer, self.progress); self.it.drive(consumer) } fn with_producer>( self, callback: CB, ) -> >::Output { return self.it.with_producer(Callback { callback, progress: self.progress, }); struct Callback { callback: CB, progress: ProgressBar, } impl> ProducerCallback for Callback { type Output = CB::Output; fn callback

(self, base: P) -> CB::Output where P: Producer, { let producer = ProgressProducer { base, progress: self.progress, }; self.callback.callback(producer) } } } } struct ProgressProducer { base: T, progress: ProgressBar, } impl> Producer for ProgressProducer

{ type Item = T; type IntoIter = ProgressBarIter; fn into_iter(self) -> Self::IntoIter { ProgressBarIter { it: self.base.into_iter(), progress: self.progress, } } fn min_len(&self) -> usize { self.base.min_len() } fn max_len(&self) -> usize { self.base.max_len() } fn split_at(self, index: usize) -> (Self, Self) { let (left, right) = self.base.split_at(index); ( ProgressProducer { base: left, progress: self.progress.clone(), }, ProgressProducer { base: right, progress: self.progress, }, ) } } struct ProgressConsumer { base: C, progress: ProgressBar, } impl ProgressConsumer { fn new(base: C, progress: ProgressBar) -> Self { ProgressConsumer { base, progress } } } impl> Consumer for ProgressConsumer { type Folder = ProgressFolder; type Reducer = C::Reducer; type Result = C::Result; fn split_at(self, index: usize) -> (Self, Self, Self::Reducer) { let (left, right, reducer) = self.base.split_at(index); ( ProgressConsumer::new(left, self.progress.clone()), ProgressConsumer::new(right, self.progress), reducer, ) } fn into_folder(self) -> Self::Folder { ProgressFolder { base: self.base.into_folder(), progress: self.progress, } } fn full(&self) -> bool { self.base.full() } } impl> UnindexedConsumer for ProgressConsumer { fn split_off_left(&self) -> Self { ProgressConsumer::new(self.base.split_off_left(), self.progress.clone()) } fn to_reducer(&self) -> Self::Reducer { self.base.to_reducer() } } struct ProgressFolder { base: C, progress: ProgressBar, } impl> Folder for ProgressFolder { type Result = C::Result; fn consume(self, item: T) -> Self { self.progress.inc(1); ProgressFolder { base: self.base.consume(item), progress: self.progress, } } fn complete(self) -> C::Result { self.base.complete() } fn full(&self) -> bool { self.base.full() } } impl> ParallelIterator for ProgressBarIter { type Item = S; fn drive_unindexed>(self, consumer: C) -> C::Result { let consumer1 = ProgressConsumer::new(consumer, self.progress.clone()); self.it.drive_unindexed(consumer1) } } #[cfg(test)] mod test { use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use crate::{ParallelProgressIterator, ProgressBar, ProgressBarIter, ProgressStyle}; #[test] fn it_can_wrap_a_parallel_iterator() { let v = vec![1, 2, 3]; fn wrap<'a, T: ParallelIterator>(it: ProgressBarIter) { assert_eq!(it.map(|x| x * 2).collect::>(), vec![2, 4, 6]); } wrap(v.par_iter().progress_count(3)); wrap({ let pb = ProgressBar::new(v.len() as u64); v.par_iter().progress_with(pb) }); wrap({ let style = ProgressStyle::default_bar() .template("{wide_bar:.red} {percent}/100%") .unwrap(); v.par_iter().progress_with_style(style) }); } } indicatif-0.17.7/src/state.rs000064400000000000000000000657651046102023000141520ustar 00000000000000use std::borrow::Cow; use std::io; use std::sync::Arc; use std::time::Duration; #[cfg(not(target_arch = "wasm32"))] use std::time::Instant; #[cfg(target_arch = "wasm32")] use instant::Instant; use portable_atomic::{AtomicU64, AtomicU8, Ordering}; use crate::draw_target::ProgressDrawTarget; use crate::style::ProgressStyle; pub(crate) struct BarState { pub(crate) draw_target: ProgressDrawTarget, pub(crate) on_finish: ProgressFinish, pub(crate) style: ProgressStyle, pub(crate) state: ProgressState, pub(crate) tab_width: usize, } impl BarState { pub(crate) fn new( len: Option, draw_target: ProgressDrawTarget, pos: Arc, ) -> Self { Self { draw_target, on_finish: ProgressFinish::default(), style: ProgressStyle::default_bar(), state: ProgressState::new(len, pos), tab_width: DEFAULT_TAB_WIDTH, } } /// Finishes the progress bar using the [`ProgressFinish`] behavior stored /// in the [`ProgressStyle`]. pub(crate) fn finish_using_style(&mut self, now: Instant, finish: ProgressFinish) { self.state.status = Status::DoneVisible; match finish { ProgressFinish::AndLeave => { if let Some(len) = self.state.len { self.state.pos.set(len); } } ProgressFinish::WithMessage(msg) => { if let Some(len) = self.state.len { self.state.pos.set(len); } self.state.message = TabExpandedString::new(msg, self.tab_width); } ProgressFinish::AndClear => { if let Some(len) = self.state.len { self.state.pos.set(len); } self.state.status = Status::DoneHidden; } ProgressFinish::Abandon => {} ProgressFinish::AbandonWithMessage(msg) => { self.state.message = TabExpandedString::new(msg, self.tab_width); } } // There's no need to update the estimate here; once the `status` is no longer // `InProgress`, we will use the length and elapsed time to estimate. let _ = self.draw(true, now); } pub(crate) fn reset(&mut self, now: Instant, mode: Reset) { // Always reset the estimator; this is the only reset that will occur if mode is // `Reset::Eta`. self.state.est.reset(now); if let Reset::Elapsed | Reset::All = mode { self.state.started = now; } if let Reset::All = mode { self.state.pos.reset(now); self.state.status = Status::InProgress; for tracker in self.style.format_map.values_mut() { tracker.reset(&self.state, now); } let _ = self.draw(false, now); } } pub(crate) fn update(&mut self, now: Instant, f: impl FnOnce(&mut ProgressState), tick: bool) { f(&mut self.state); if tick { self.tick(now); } } pub(crate) fn set_length(&mut self, now: Instant, len: u64) { self.state.len = Some(len); self.update_estimate_and_draw(now); } pub(crate) fn inc_length(&mut self, now: Instant, delta: u64) { if let Some(len) = self.state.len { self.state.len = Some(len.saturating_add(delta)); } self.update_estimate_and_draw(now); } pub(crate) fn set_tab_width(&mut self, tab_width: usize) { self.tab_width = tab_width; self.state.message.set_tab_width(tab_width); self.state.prefix.set_tab_width(tab_width); self.style.set_tab_width(tab_width); } pub(crate) fn set_style(&mut self, style: ProgressStyle) { self.style = style; self.style.set_tab_width(self.tab_width); } pub(crate) fn tick(&mut self, now: Instant) { self.state.tick = self.state.tick.saturating_add(1); self.update_estimate_and_draw(now); } pub(crate) fn update_estimate_and_draw(&mut self, now: Instant) { let pos = self.state.pos.pos.load(Ordering::Relaxed); self.state.est.record(pos, now); for tracker in self.style.format_map.values_mut() { tracker.tick(&self.state, now); } let _ = self.draw(false, now); } pub(crate) fn println(&mut self, now: Instant, msg: &str) { let width = self.draw_target.width(); let mut drawable = match self.draw_target.drawable(true, now) { Some(drawable) => drawable, None => return, }; let mut draw_state = drawable.state(); let lines: Vec = msg.lines().map(Into::into).collect(); // Empty msg should trigger newline as we are in println if lines.is_empty() { draw_state.lines.push(String::new()); } else { draw_state.lines.extend(lines); } draw_state.orphan_lines_count = draw_state.lines.len(); if !matches!(self.state.status, Status::DoneHidden) { self.style .format_state(&self.state, &mut draw_state.lines, width); } drop(draw_state); let _ = drawable.draw(); } pub(crate) fn suspend R, R>(&mut self, now: Instant, f: F) -> R { if let Some((state, _)) = self.draw_target.remote() { return state.write().unwrap().suspend(f, now); } if let Some(drawable) = self.draw_target.drawable(true, now) { let _ = drawable.clear(); } let ret = f(); let _ = self.draw(true, Instant::now()); ret } pub(crate) fn draw(&mut self, mut force_draw: bool, now: Instant) -> io::Result<()> { let width = self.draw_target.width(); // `|= self.is_finished()` should not be needed here, but we used to always draw for // finished progress bars, so it's kept as to not cause compatibility issues in weird cases. force_draw |= self.state.is_finished(); let mut drawable = match self.draw_target.drawable(force_draw, now) { Some(drawable) => drawable, None => return Ok(()), }; let mut draw_state = drawable.state(); if !matches!(self.state.status, Status::DoneHidden) { self.style .format_state(&self.state, &mut draw_state.lines, width); } drop(draw_state); drawable.draw() } } impl Drop for BarState { fn drop(&mut self) { // Progress bar is already finished. Do not need to do anything other than notify // the `MultiProgress` that we're now a zombie. if self.state.is_finished() { self.draw_target.mark_zombie(); return; } self.finish_using_style(Instant::now(), self.on_finish.clone()); // Notify the `MultiProgress` that we're now a zombie. self.draw_target.mark_zombie(); } } pub(crate) enum Reset { Eta, Elapsed, All, } /// The state of a progress bar at a moment in time. #[non_exhaustive] pub struct ProgressState { pos: Arc, len: Option, pub(crate) tick: u64, pub(crate) started: Instant, status: Status, est: Estimator, pub(crate) message: TabExpandedString, pub(crate) prefix: TabExpandedString, } impl ProgressState { pub(crate) fn new(len: Option, pos: Arc) -> Self { let now = Instant::now(); Self { pos, len, tick: 0, status: Status::InProgress, started: now, est: Estimator::new(now), message: TabExpandedString::NoTabs("".into()), prefix: TabExpandedString::NoTabs("".into()), } } /// Indicates that the progress bar finished. pub fn is_finished(&self) -> bool { match self.status { Status::InProgress => false, Status::DoneVisible => true, Status::DoneHidden => true, } } /// Returns the completion as a floating-point number between 0 and 1 pub fn fraction(&self) -> f32 { let pos = self.pos.pos.load(Ordering::Relaxed); let pct = match (pos, self.len) { (_, None) => 0.0, (_, Some(0)) => 1.0, (0, _) => 0.0, (pos, Some(len)) => pos as f32 / len as f32, }; pct.clamp(0.0, 1.0) } /// The expected ETA pub fn eta(&self) -> Duration { if self.is_finished() { return Duration::new(0, 0); } let len = match self.len { Some(len) => len, None => return Duration::new(0, 0), }; let pos = self.pos.pos.load(Ordering::Relaxed); let sps = self.est.steps_per_second(Instant::now()); // Infinite duration should only ever happen at the beginning, so in this case it's okay to // just show an ETA of 0 until progress starts to occur. if sps == 0.0 { return Duration::new(0, 0); } secs_to_duration(len.saturating_sub(pos) as f64 / sps) } /// The expected total duration (that is, elapsed time + expected ETA) pub fn duration(&self) -> Duration { if self.len.is_none() || self.is_finished() { return Duration::new(0, 0); } self.started.elapsed().saturating_add(self.eta()) } /// The number of steps per second pub fn per_sec(&self) -> f64 { if let Status::InProgress = self.status { self.est.steps_per_second(Instant::now()) } else { let len = self.len.unwrap_or_else(|| self.pos()); len as f64 / self.started.elapsed().as_secs_f64() } } pub fn elapsed(&self) -> Duration { self.started.elapsed() } pub fn pos(&self) -> u64 { self.pos.pos.load(Ordering::Relaxed) } pub fn set_pos(&mut self, pos: u64) { self.pos.set(pos); } #[allow(clippy::len_without_is_empty)] pub fn len(&self) -> Option { self.len } pub fn set_len(&mut self, len: u64) { self.len = Some(len); } } #[derive(Debug, PartialEq, Eq, Clone)] pub(crate) enum TabExpandedString { NoTabs(Cow<'static, str>), WithTabs { original: Cow<'static, str>, expanded: String, tab_width: usize, }, } impl TabExpandedString { pub(crate) fn new(s: Cow<'static, str>, tab_width: usize) -> Self { let expanded = s.replace('\t', &" ".repeat(tab_width)); if s == expanded { Self::NoTabs(s) } else { Self::WithTabs { original: s, expanded, tab_width, } } } pub(crate) fn expanded(&self) -> &str { match &self { Self::NoTabs(s) => { debug_assert!(!s.contains('\t')); s } Self::WithTabs { expanded, .. } => expanded, } } pub(crate) fn set_tab_width(&mut self, new_tab_width: usize) { if let Self::WithTabs { original, expanded, tab_width, } = self { if *tab_width != new_tab_width { *tab_width = new_tab_width; *expanded = original.replace('\t', &" ".repeat(new_tab_width)); } } } } /// Double-smoothed exponentially weighted estimator /// /// This uses an exponentially weighted *time-based* estimator, meaning that it exponentially /// downweights old data based on its age. The rate at which this occurs is currently a constant /// value of 15 seconds for 90% weighting. This means that all data older than 15 seconds has a /// collective weight of 0.1 in the estimate, and all data older than 30 seconds has a collective /// weight of 0.01, and so on. /// /// The primary value exposed by `Estimator` is `steps_per_second`. This value is doubly-smoothed, /// meaning that is the result of using an exponentially weighted estimator (as described above) to /// estimate the value of another exponentially weighted estimator, which estimates the value of /// the raw data. /// /// The purpose of this extra smoothing step is to reduce instantaneous fluctations in the estimate /// when large updates are received. Without this, estimates might have a large spike followed by a /// slow asymptotic approach to zero (until the next spike). #[derive(Debug)] pub(crate) struct Estimator { smoothed_steps_per_sec: f64, double_smoothed_steps_per_sec: f64, prev_steps: u64, prev_time: Instant, start_time: Instant, } impl Estimator { fn new(now: Instant) -> Self { Self { smoothed_steps_per_sec: 0.0, double_smoothed_steps_per_sec: 0.0, prev_steps: 0, prev_time: now, start_time: now, } } fn record(&mut self, new_steps: u64, now: Instant) { // sanity check: don't record data if time or steps have not advanced if new_steps <= self.prev_steps || now <= self.prev_time { // Reset on backwards seek to prevent breakage from seeking to the end for length determination // See https://github.com/console-rs/indicatif/issues/480 if new_steps < self.prev_steps { self.prev_steps = new_steps; self.reset(now); } return; } let delta_steps = new_steps - self.prev_steps; let delta_t = duration_to_secs(now - self.prev_time); // the rate of steps we saw in this update let new_steps_per_second = delta_steps as f64 / delta_t; // update the estimate: a weighted average of the old estimate and new data let weight = estimator_weight(delta_t); self.smoothed_steps_per_sec = self.smoothed_steps_per_sec * weight + new_steps_per_second * (1.0 - weight); // An iterative estimate like `smoothed_steps_per_sec` is supposed to be an exponentially // weighted average from t=0 back to t=-inf; Since we initialize it to 0, we neglect the // (non-existent) samples in the weighted average prior to the first one, so the resulting // average must be normalized. We normalize the single estimate here in order to use it as // a source for the double smoothed estimate. See comment on normalization in // `steps_per_second` for details. let delta_t_start = duration_to_secs(now - self.start_time); let total_weight = 1.0 - estimator_weight(delta_t_start); let normalized_smoothed_steps_per_sec = self.smoothed_steps_per_sec / total_weight; // determine the double smoothed value (EWA smoothing of the single EWA) self.double_smoothed_steps_per_sec = self.double_smoothed_steps_per_sec * weight + normalized_smoothed_steps_per_sec * (1.0 - weight); self.prev_steps = new_steps; self.prev_time = now; } /// Reset the state of the estimator. Once reset, estimates will not depend on any data prior /// to `now`. This does not reset the stored position of the progress bar. pub(crate) fn reset(&mut self, now: Instant) { self.smoothed_steps_per_sec = 0.0; self.double_smoothed_steps_per_sec = 0.0; // only reset prev_time, not prev_steps self.prev_time = now; self.start_time = now; } /// Average time per step in seconds, using double exponential smoothing fn steps_per_second(&self, now: Instant) -> f64 { // Because the value stored in the Estimator is only updated when the Estimator receives an // update, this value will become stuck if progress stalls. To return an accurate estimate, // we determine how much time has passed since the last update, and treat this as a // pseudo-update with 0 steps. let delta_t = duration_to_secs(now - self.prev_time); let reweight = estimator_weight(delta_t); // Normalization of estimates: // // The raw estimate is a single value (smoothed_steps_per_second) that is iteratively // updated. At each update, the previous value of the estimate is downweighted according to // its age, receiving the iterative weight W(t) = 0.1 ^ (t/15). // // Since W(Sum(t_n)) = Prod(W(t_n)), the total weight of a sample after a series of // iterative steps is simply W(t_e) - W(t_b), where t_e is the time since the end of the // sample, and t_b is the time since the beginning. The resulting estimate is therefore a // weighted average with sample weights W(t_e) - W(t_b). // // Notice that the weighting function generates sample weights that sum to 1 only when the // sample times span from t=0 to t=inf; but this is not the case. We have a first sample // with finite, positive t_b = t_f. In the raw estimate, we handle times prior to t_f by // setting an initial value of 0, meaning that these (non-existent) samples have no weight. // // Therefore, the raw estimate must be normalized by dividing it by the sum of the weights // in the weighted average. This sum is just W(0) - W(t_f), where t_f is the time since the // first sample, and W(0) = 1. let delta_t_start = duration_to_secs(now - self.start_time); let total_weight = 1.0 - estimator_weight(delta_t_start); // Generate updated values for `smoothed_steps_per_sec` and `double_smoothed_steps_per_sec` // (sps and dsps) without storing them. Note that we normalize sps when using it as a // source to update dsps, and then normalize dsps itself before returning it. let sps = self.smoothed_steps_per_sec * reweight / total_weight; let dsps = self.double_smoothed_steps_per_sec * reweight + sps * (1.0 - reweight); dsps / total_weight } } pub(crate) struct AtomicPosition { pub(crate) pos: AtomicU64, capacity: AtomicU8, prev: AtomicU64, start: Instant, } impl AtomicPosition { pub(crate) fn new() -> Self { Self { pos: AtomicU64::new(0), capacity: AtomicU8::new(MAX_BURST), prev: AtomicU64::new(0), start: Instant::now(), } } pub(crate) fn allow(&self, now: Instant) -> bool { if now < self.start { return false; } let mut capacity = self.capacity.load(Ordering::Acquire); // `prev` is the number of ms after `self.started` we last returned `true`, in ns let prev = self.prev.load(Ordering::Acquire); // `elapsed` is the number of ns since `self.started` let elapsed = (now - self.start).as_nanos() as u64; // `diff` is the number of ns since we last returned `true` let diff = elapsed.saturating_sub(prev); // If `capacity` is 0 and not enough time (1ms) has passed since `prev` // to add new capacity, return `false`. The goal of this method is to // make this decision as efficient as possible. if capacity == 0 && diff < INTERVAL { return false; } // We now calculate `new`, the number of ms, in ns, since we last returned `true`, // and `remainder`, which represents a number of ns less than 1ms which we cannot // convert into capacity now, so we're saving it for later. We do this by // substracting this from `elapsed` before storing it into `self.prev`. let (new, remainder) = ((diff / INTERVAL), (diff % INTERVAL)); // We add `new` to `capacity`, subtract one for returning `true` from here, // then make sure it does not exceed a maximum of `MAX_BURST`. capacity = Ord::min(MAX_BURST as u128, (capacity as u128) + (new as u128) - 1) as u8; // Then, we just store `capacity` and `prev` atomically for the next iteration self.capacity.store(capacity, Ordering::Release); self.prev.store(elapsed - remainder, Ordering::Release); true } fn reset(&self, now: Instant) { self.set(0); let elapsed = (now.saturating_duration_since(self.start)).as_millis() as u64; self.prev.store(elapsed, Ordering::Release); } pub(crate) fn inc(&self, delta: u64) { self.pos.fetch_add(delta, Ordering::SeqCst); } pub(crate) fn set(&self, pos: u64) { self.pos.store(pos, Ordering::Release); } } const INTERVAL: u64 = 1_000_000; const MAX_BURST: u8 = 10; /// Behavior of a progress bar when it is finished /// /// This is invoked when a [`ProgressBar`] or [`ProgressBarIter`] completes and /// [`ProgressBar::is_finished`] is false. /// /// [`ProgressBar`]: crate::ProgressBar /// [`ProgressBarIter`]: crate::ProgressBarIter /// [`ProgressBar::is_finished`]: crate::ProgressBar::is_finished #[derive(Clone, Debug)] pub enum ProgressFinish { /// Finishes the progress bar and leaves the current message /// /// Same behavior as calling [`ProgressBar::finish()`](crate::ProgressBar::finish). AndLeave, /// Finishes the progress bar and sets a message /// /// Same behavior as calling [`ProgressBar::finish_with_message()`](crate::ProgressBar::finish_with_message). WithMessage(Cow<'static, str>), /// Finishes the progress bar and completely clears it (this is the default) /// /// Same behavior as calling [`ProgressBar::finish_and_clear()`](crate::ProgressBar::finish_and_clear). AndClear, /// Finishes the progress bar and leaves the current message and progress /// /// Same behavior as calling [`ProgressBar::abandon()`](crate::ProgressBar::abandon). Abandon, /// Finishes the progress bar and sets a message, and leaves the current progress /// /// Same behavior as calling [`ProgressBar::abandon_with_message()`](crate::ProgressBar::abandon_with_message). AbandonWithMessage(Cow<'static, str>), } impl Default for ProgressFinish { fn default() -> Self { Self::AndClear } } /// Get the appropriate dilution weight for Estimator data given the data's age (in seconds) /// /// Whenever an update occurs, we will create a new estimate using a weight `w_i` like so: /// /// ```math /// = * w_i + * (1 - w_i) /// ``` /// /// In other words, the new estimate is a weighted average of the previous estimate and the new /// data. We want to choose weights such that for any set of samples where `t_0, t_1, ...` are /// the durations of the samples: /// /// ```math /// Sum(t_i) = ews ==> Prod(w_i) = 0.1 /// ``` /// /// With this constraint it is easy to show that /// /// ```math /// w_i = 0.1 ^ (t_i / ews) /// ``` /// /// Notice that the constraint implies that estimates are independent of the durations of the /// samples, a very useful feature. fn estimator_weight(age: f64) -> f64 { const EXPONENTIAL_WEIGHTING_SECONDS: f64 = 15.0; 0.1_f64.powf(age / EXPONENTIAL_WEIGHTING_SECONDS) } fn duration_to_secs(d: Duration) -> f64 { d.as_secs() as f64 + f64::from(d.subsec_nanos()) / 1_000_000_000f64 } fn secs_to_duration(s: f64) -> Duration { let secs = s.trunc() as u64; let nanos = (s.fract() * 1_000_000_000f64) as u32; Duration::new(secs, nanos) } #[derive(Debug)] pub(crate) enum Status { InProgress, DoneVisible, DoneHidden, } pub(crate) const DEFAULT_TAB_WIDTH: usize = 8; #[cfg(test)] mod tests { use super::*; use crate::ProgressBar; // https://github.com/rust-lang/rust-clippy/issues/10281 #[allow(clippy::uninlined_format_args)] #[test] fn test_steps_per_second() { let test_rate = |items_per_second| { let mut now = Instant::now(); let mut est = Estimator::new(now); let mut pos = 0; for _ in 0..20 { pos += items_per_second; now += Duration::from_secs(1); est.record(pos, now); } let avg_steps_per_second = est.steps_per_second(now); assert!(avg_steps_per_second > 0.0); assert!(avg_steps_per_second.is_finite()); let absolute_error = (avg_steps_per_second - items_per_second as f64).abs(); let relative_error = absolute_error / items_per_second as f64; assert!( relative_error < 1.0 / 1e9, "Expected rate: {}, actual: {}, relative error: {}", items_per_second, avg_steps_per_second, relative_error ); }; test_rate(1); test_rate(1_000); test_rate(1_000_000); test_rate(1_000_000_000); test_rate(1_000_000_001); test_rate(100_000_000_000); test_rate(1_000_000_000_000); test_rate(100_000_000_000_000); test_rate(1_000_000_000_000_000); } #[test] fn test_double_exponential_ave() { let mut now = Instant::now(); let mut est = Estimator::new(now); let mut pos = 0; // note: this is the default weight set in the Estimator let weight = 15; for _ in 0..weight { pos += 1; now += Duration::from_secs(1); est.record(pos, now); } now += Duration::from_secs(weight); // The first level EWA: // -> 90% weight @ 0 eps, 9% weight @ 1 eps, 1% weight @ 0 eps // -> then normalized by deweighting the 1% weight (before -30 seconds) let single_target = 0.09 / 0.99; // The second level EWA: // -> same logic as above, but using the first level EWA as the source let double_target = (0.9 * single_target + 0.09) / 0.99; assert_eq!(est.steps_per_second(now), double_target); } #[test] fn test_estimator_rewind_position() { let mut now = Instant::now(); let mut est = Estimator::new(now); now += Duration::from_secs(1); est.record(1, now); // should not panic now += Duration::from_secs(1); est.record(0, now); // check that reset occurred (estimator at 1 event per sec) now += Duration::from_secs(1); est.record(1, now); assert_eq!(est.steps_per_second(now), 1.0); // check that progress bar handles manual seeking let pb = ProgressBar::hidden(); pb.set_length(10); pb.set_position(1); pb.tick(); // Should not panic. pb.set_position(0); } #[test] fn test_reset_eta() { let mut now = Instant::now(); let mut est = Estimator::new(now); // two per second, then reset now += Duration::from_secs(1); est.record(2, now); est.reset(now); // now one per second, and verify now += Duration::from_secs(1); est.record(3, now); assert_eq!(est.steps_per_second(now), 1.0); } #[test] fn test_duration_stuff() { let duration = Duration::new(42, 100_000_000); let secs = duration_to_secs(duration); assert_eq!(secs_to_duration(secs), duration); } #[test] fn test_atomic_position_large_time_difference() { let atomic_position = AtomicPosition::new(); let later = atomic_position.start + Duration::from_nanos(INTERVAL * u64::from(u8::MAX)); // Should not panic. atomic_position.allow(later); } } indicatif-0.17.7/src/style.rs000064400000000000000000001057141046102023000141570ustar 00000000000000use std::collections::HashMap; use std::fmt::{self, Write}; use std::mem; #[cfg(not(target_arch = "wasm32"))] use std::time::Instant; use console::{measure_text_width, Style}; #[cfg(target_arch = "wasm32")] use instant::Instant; #[cfg(feature = "unicode-segmentation")] use unicode_segmentation::UnicodeSegmentation; use crate::format::{ BinaryBytes, DecimalBytes, FormattedDuration, HumanBytes, HumanCount, HumanDuration, HumanFloatCount, }; use crate::state::{ProgressState, TabExpandedString, DEFAULT_TAB_WIDTH}; #[derive(Clone)] pub struct ProgressStyle { tick_strings: Vec>, progress_chars: Vec>, template: Template, // how unicode-big each char in progress_chars is char_width: usize, tab_width: usize, pub(crate) format_map: HashMap<&'static str, Box>, } #[cfg(feature = "unicode-segmentation")] fn segment(s: &str) -> Vec> { UnicodeSegmentation::graphemes(s, true) .map(|s| s.into()) .collect() } #[cfg(not(feature = "unicode-segmentation"))] fn segment(s: &str) -> Vec> { s.chars().map(|x| x.to_string().into()).collect() } #[cfg(feature = "unicode-width")] fn measure(s: &str) -> usize { unicode_width::UnicodeWidthStr::width(s) } #[cfg(not(feature = "unicode-width"))] fn measure(s: &str) -> usize { s.chars().count() } /// finds the unicode-aware width of the passed grapheme cluters /// panics on an empty parameter, or if the characters are not equal-width fn width(c: &[Box]) -> usize { c.iter() .map(|s| measure(s.as_ref())) .fold(None, |acc, new| { match acc { None => return Some(new), Some(old) => assert_eq!(old, new, "got passed un-equal width progress characters"), } acc }) .unwrap() } impl ProgressStyle { /// Returns the default progress bar style for bars pub fn default_bar() -> Self { Self::new(Template::from_str("{wide_bar} {pos}/{len}").unwrap()) } /// Returns the default progress bar style for spinners pub fn default_spinner() -> Self { Self::new(Template::from_str("{spinner} {msg}").unwrap()) } /// Sets the template string for the progress bar /// /// Review the [list of template keys](../index.html#templates) for more information. pub fn with_template(template: &str) -> Result { Ok(Self::new(Template::from_str(template)?)) } pub(crate) fn set_tab_width(&mut self, new_tab_width: usize) { self.tab_width = new_tab_width; self.template.set_tab_width(new_tab_width); } fn new(template: Template) -> Self { let progress_chars = segment("█░"); let char_width = width(&progress_chars); Self { tick_strings: "⠁⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈ " .chars() .map(|c| c.to_string().into()) .collect(), progress_chars, char_width, template, format_map: HashMap::default(), tab_width: DEFAULT_TAB_WIDTH, } } /// Sets the tick character sequence for spinners /// /// Note that the last character is used as the [final tick string][Self::get_final_tick_str()]. /// At least two characters are required to provide a non-final and final state. pub fn tick_chars(mut self, s: &str) -> Self { self.tick_strings = s.chars().map(|c| c.to_string().into()).collect(); // Format bar will panic with some potentially confusing message, better to panic here // with a message explicitly informing of the problem assert!( self.tick_strings.len() >= 2, "at least 2 tick chars required" ); self } /// Sets the tick string sequence for spinners /// /// Note that the last string is used as the [final tick string][Self::get_final_tick_str()]. /// At least two strings are required to provide a non-final and final state. pub fn tick_strings(mut self, s: &[&str]) -> Self { self.tick_strings = s.iter().map(|s| s.to_string().into()).collect(); // Format bar will panic with some potentially confusing message, better to panic here // with a message explicitly informing of the problem assert!( self.progress_chars.len() >= 2, "at least 2 tick strings required" ); self } /// Sets the progress characters `(filled, current, to do)` /// /// You can pass more than three for a more detailed display. /// All passed grapheme clusters need to be of equal width. pub fn progress_chars(mut self, s: &str) -> Self { self.progress_chars = segment(s); // Format bar will panic with some potentially confusing message, better to panic here // with a message explicitly informing of the problem assert!( self.progress_chars.len() >= 2, "at least 2 progress chars required" ); self.char_width = width(&self.progress_chars); self } /// Adds a custom key that owns a [`ProgressTracker`] to the template pub fn with_key(mut self, key: &'static str, f: S) -> Self { self.format_map.insert(key, Box::new(f)); self } /// Sets the template string for the progress bar /// /// Review the [list of template keys](../index.html#templates) for more information. pub fn template(mut self, s: &str) -> Result { self.template = Template::from_str(s)?; Ok(self) } fn current_tick_str(&self, state: &ProgressState) -> &str { match state.is_finished() { true => self.get_final_tick_str(), false => self.get_tick_str(state.tick), } } /// Returns the tick string for a given number pub fn get_tick_str(&self, idx: u64) -> &str { &self.tick_strings[(idx as usize) % (self.tick_strings.len() - 1)] } /// Returns the tick string for the finished state pub fn get_final_tick_str(&self) -> &str { &self.tick_strings[self.tick_strings.len() - 1] } fn format_bar(&self, fract: f32, width: usize, alt_style: Option<&Style>) -> BarDisplay<'_> { // The number of clusters from progress_chars to write (rounding down). let width = width / self.char_width; // The number of full clusters (including a fractional component for a partially-full one). let fill = fract * width as f32; // The number of entirely full clusters (by truncating `fill`). let entirely_filled = fill as usize; // 1 if the bar is not entirely empty or full (meaning we need to draw the "current" // character between the filled and "to do" segment), 0 otherwise. let head = usize::from(fill > 0.0 && entirely_filled < width); let cur = if head == 1 { // Number of fine-grained progress entries in progress_chars. let n = self.progress_chars.len().saturating_sub(2); let cur_char = if n <= 1 { // No fine-grained entries. 1 is the single "current" entry if we have one, the "to // do" entry if not. 1 } else { // Pick a fine-grained entry, ranging from the last one (n) if the fractional part // of fill is 0 to the first one (1) if the fractional part of fill is almost 1. n.saturating_sub((fill.fract() * n as f32) as usize) }; Some(cur_char) } else { None }; // Number of entirely empty clusters needed to fill the bar up to `width`. let bg = width.saturating_sub(entirely_filled).saturating_sub(head); let rest = RepeatedStringDisplay { str: &self.progress_chars[self.progress_chars.len() - 1], num: bg, }; BarDisplay { chars: &self.progress_chars, filled: entirely_filled, cur, rest: alt_style.unwrap_or(&Style::new()).apply_to(rest), } } pub(crate) fn format_state( &self, state: &ProgressState, lines: &mut Vec, target_width: u16, ) { let mut cur = String::new(); let mut buf = String::new(); let mut wide = None; let pos = state.pos(); let len = state.len().unwrap_or(pos); for part in &self.template.parts { match part { TemplatePart::Placeholder { key, align, width, truncate, style, alt_style, } => { buf.clear(); if let Some(tracker) = self.format_map.get(key.as_str()) { tracker.write(state, &mut TabRewriter(&mut buf, self.tab_width)); } else { match key.as_str() { "wide_bar" => { wide = Some(WideElement::Bar { alt_style }); buf.push('\x00'); } "bar" => buf .write_fmt(format_args!( "{}", self.format_bar( state.fraction(), width.unwrap_or(20) as usize, alt_style.as_ref(), ) )) .unwrap(), "spinner" => buf.push_str(self.current_tick_str(state)), "wide_msg" => { wide = Some(WideElement::Message { align }); buf.push('\x00'); } "msg" => buf.push_str(state.message.expanded()), "prefix" => buf.push_str(state.prefix.expanded()), "pos" => buf.write_fmt(format_args!("{pos}")).unwrap(), "human_pos" => { buf.write_fmt(format_args!("{}", HumanCount(pos))).unwrap(); } "len" => buf.write_fmt(format_args!("{len}")).unwrap(), "human_len" => { buf.write_fmt(format_args!("{}", HumanCount(len))).unwrap(); } "percent" => buf .write_fmt(format_args!("{:.*}", 0, state.fraction() * 100f32)) .unwrap(), "bytes" => buf.write_fmt(format_args!("{}", HumanBytes(pos))).unwrap(), "total_bytes" => { buf.write_fmt(format_args!("{}", HumanBytes(len))).unwrap(); } "decimal_bytes" => buf .write_fmt(format_args!("{}", DecimalBytes(pos))) .unwrap(), "decimal_total_bytes" => buf .write_fmt(format_args!("{}", DecimalBytes(len))) .unwrap(), "binary_bytes" => { buf.write_fmt(format_args!("{}", BinaryBytes(pos))).unwrap(); } "binary_total_bytes" => { buf.write_fmt(format_args!("{}", BinaryBytes(len))).unwrap(); } "elapsed_precise" => buf .write_fmt(format_args!("{}", FormattedDuration(state.elapsed()))) .unwrap(), "elapsed" => buf .write_fmt(format_args!("{:#}", HumanDuration(state.elapsed()))) .unwrap(), "per_sec" => buf .write_fmt(format_args!("{}/s", HumanFloatCount(state.per_sec()))) .unwrap(), "bytes_per_sec" => buf .write_fmt(format_args!("{}/s", HumanBytes(state.per_sec() as u64))) .unwrap(), "binary_bytes_per_sec" => buf .write_fmt(format_args!( "{}/s", BinaryBytes(state.per_sec() as u64) )) .unwrap(), "eta_precise" => buf .write_fmt(format_args!("{}", FormattedDuration(state.eta()))) .unwrap(), "eta" => buf .write_fmt(format_args!("{:#}", HumanDuration(state.eta()))) .unwrap(), "duration_precise" => buf .write_fmt(format_args!("{}", FormattedDuration(state.duration()))) .unwrap(), "duration" => buf .write_fmt(format_args!("{:#}", HumanDuration(state.duration()))) .unwrap(), _ => (), } }; match width { Some(width) => { let padded = PaddedStringDisplay { str: &buf, width: *width as usize, align: *align, truncate: *truncate, }; match style { Some(s) => cur .write_fmt(format_args!("{}", s.apply_to(padded))) .unwrap(), None => cur.write_fmt(format_args!("{padded}")).unwrap(), } } None => match style { Some(s) => cur.write_fmt(format_args!("{}", s.apply_to(&buf))).unwrap(), None => cur.push_str(&buf), }, } } TemplatePart::Literal(s) => cur.push_str(s.expanded()), TemplatePart::NewLine => { self.push_line(lines, &mut cur, state, &mut buf, target_width, &wide); } } } if !cur.is_empty() { self.push_line(lines, &mut cur, state, &mut buf, target_width, &wide); } } fn push_line( &self, lines: &mut Vec, cur: &mut String, state: &ProgressState, buf: &mut String, target_width: u16, wide: &Option, ) { let expanded = match wide { Some(inner) => inner.expand(mem::take(cur), self, state, buf, target_width), None => mem::take(cur), }; // If there are newlines, we need to split them up // and add the lines separately so that they're counted // correctly on re-render. for (i, line) in expanded.split('\n').enumerate() { // No newlines found in this case if i == 0 && line.len() == expanded.len() { lines.push(expanded); break; } lines.push(line.to_string()); } } } struct TabRewriter<'a>(&'a mut dyn fmt::Write, usize); impl Write for TabRewriter<'_> { fn write_str(&mut self, s: &str) -> fmt::Result { self.0 .write_str(s.replace('\t', &" ".repeat(self.1)).as_str()) } } #[derive(Clone, Copy)] enum WideElement<'a> { Bar { alt_style: &'a Option