summaryrefslogtreecommitdiffstats
path: root/vendor/prodash
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:35 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:35 +0000
commit7e5d7eea9c580ef4b41a765bde624af431942b96 (patch)
tree2c0d9ca12878fc4525650aa4e54d77a81a07cc09 /vendor/prodash
parentAdding debian version 1.70.0+dfsg1-9. (diff)
downloadrustc-7e5d7eea9c580ef4b41a765bde624af431942b96.tar.xz
rustc-7e5d7eea9c580ef4b41a765bde624af431942b96.zip
Merging upstream version 1.70.0+dfsg2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/prodash')
-rw-r--r--vendor/prodash/.cargo-checksum.json1
-rw-r--r--vendor/prodash/CHANGELOG.md2593
-rw-r--r--vendor/prodash/Cargo.lock1212
-rw-r--r--vendor/prodash/Cargo.toml240
-rw-r--r--vendor/prodash/LICENSE.md25
-rw-r--r--vendor/prodash/README.md120
-rw-r--r--vendor/prodash/src/lib.rs86
-rw-r--r--vendor/prodash/src/messages.rs126
-rw-r--r--vendor/prodash/src/progress/key.rs261
-rw-r--r--vendor/prodash/src/progress/log.rs151
-rw-r--r--vendor/prodash/src/progress/mod.rs112
-rw-r--r--vendor/prodash/src/progress/utils.rs337
-rw-r--r--vendor/prodash/src/render/line/draw.rs350
-rw-r--r--vendor/prodash/src/render/line/engine.rs341
-rw-r--r--vendor/prodash/src/render/line/mod.rs10
-rw-r--r--vendor/prodash/src/render/mod.rs11
-rw-r--r--vendor/prodash/src/render/tui/draw/all.rs164
-rw-r--r--vendor/prodash/src/render/tui/draw/information.rs61
-rw-r--r--vendor/prodash/src/render/tui/draw/messages.rs150
-rw-r--r--vendor/prodash/src/render/tui/draw/mod.rs6
-rw-r--r--vendor/prodash/src/render/tui/draw/progress.rs506
-rw-r--r--vendor/prodash/src/render/tui/engine.rs258
-rw-r--r--vendor/prodash/src/render/tui/mod.rs59
-rw-r--r--vendor/prodash/src/render/tui/utils.rs25
-rw-r--r--vendor/prodash/src/throughput.rs124
-rw-r--r--vendor/prodash/src/time.rs63
-rw-r--r--vendor/prodash/src/traits.rs300
-rw-r--r--vendor/prodash/src/tree/item.rs406
-rw-r--r--vendor/prodash/src/tree/mod.rs89
-rw-r--r--vendor/prodash/src/tree/root.rs179
-rw-r--r--vendor/prodash/src/tree/tests.rs115
-rw-r--r--vendor/prodash/src/unit/bytes.rs34
-rw-r--r--vendor/prodash/src/unit/display.rs188
-rw-r--r--vendor/prodash/src/unit/duration.rs27
-rw-r--r--vendor/prodash/src/unit/human.rs47
-rw-r--r--vendor/prodash/src/unit/mod.rs140
-rw-r--r--vendor/prodash/src/unit/range.rs34
-rw-r--r--vendor/prodash/src/unit/traits.rs93
38 files changed, 9044 insertions, 0 deletions
diff --git a/vendor/prodash/.cargo-checksum.json b/vendor/prodash/.cargo-checksum.json
new file mode 100644
index 000000000..7f3429fae
--- /dev/null
+++ b/vendor/prodash/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"CHANGELOG.md":"5e08a782c967de830176d8a772120d377e67d91b044cb7752b7530a1413ee6a6","Cargo.lock":"21cd924a0050b4a933a867de38a4665896bc4ea98e33023d74606035732ba218","Cargo.toml":"15c3266652dbe2ac45d7c9a1c288757d9894fece3dc31b17391a0c3c20e7f06b","LICENSE.md":"fd6e8d4faaa8ad5856140481c2d7664ded0c92ddba62bd3d1f84fa50ba026a30","README.md":"59dc3974e189a9fb83942866113768612fcabf5dde0584ebc19fd5648b1c2050","src/lib.rs":"8ad9750d0c547f959b628d128664d3c038544ef3b615148ac641dfa0f6803413","src/messages.rs":"4226d2971f9f42102b81c05b63725a2d89ab8b83a565044b0c320cf83622abb7","src/progress/key.rs":"8603538c1a10ca8fb7e02314f981ced0305e86d9521f0793d6329c1622ccb8c6","src/progress/log.rs":"81aa863a5732ad2c0f091b2f5d09bc7a05326a7d1c61018776be63e918b847da","src/progress/mod.rs":"56e26a43472c9c7a4043f73c5a7c0b8cb0ead77aaacfec1bc7069526515256cc","src/progress/utils.rs":"be20317a095b2dc7b5af47d99ca6ffc0ac2bf2cc41f66c26324fe65ec4a58329","src/render/line/draw.rs":"28c7423dcbfff3512496867a2587a3c2c6cc546b8549bc308d6c0eeb4189411a","src/render/line/engine.rs":"0762a3ec7833e71ef81afdf82fd431daaa328b24d2959eb9491fad9a0b05ad10","src/render/line/mod.rs":"38ed88af958a5e954fcf6a3522a908348d6664326b23e05731782a06a3ceca8d","src/render/mod.rs":"9115aa8e27bbc97344ed4787d56865d1447b3e15ba6631b5ea39041e09f02c1e","src/render/tui/draw/all.rs":"619db21132be0ccae6ba3345b42623f320c85283922bc862710b55fbe1416f87","src/render/tui/draw/information.rs":"e3e7288a8e85aa2142b88f2bdbee85bab71044a6f4e427732ba271f654b50599","src/render/tui/draw/messages.rs":"b8c8c155cfb11c321704e85d5a552033c74218c117aebd2106c59933c17a4c3a","src/render/tui/draw/mod.rs":"984c39dd21170e2cf249145b2ba1716dbc3b1f0399bdd69b550c520ca158a5b0","src/render/tui/draw/progress.rs":"3762b6563e3b65727cd367336bb62fbf3317d01e94b51b1374ceb11aefb28761","src/render/tui/engine.rs":"c742325636df007896c2f0a3378ae939bfdef7eb9df5f00ee9c2190ddacf1c4a","src/render/tui/mod.rs":"d06afdae99c90643d3ff70eef6704bdc9e1a88dc92d77f6dd31b9e2a3e989aae","src/render/tui/utils.rs":"8efc35a7cb2dd3b67339efe04861103312cd17cb271263a55ba96a4898a1f3d3","src/throughput.rs":"71090e30b495c9b35d9f8e79161f5ab0ec99788bc367c6504ca8c417d1d31f3a","src/time.rs":"b87cf9eb9c0e0f618f053c19075a102ed9a8241636e32e1a86b94c4dc02e748b","src/traits.rs":"e3441df1b05831cd1a92d4048740a2500cea2d4130961ef9cf7e6b0586e8b1a1","src/tree/item.rs":"89b10dd200ba762ec85dcc11cdc4bf642e32ea7b0b0f112fbd4c63aba040a808","src/tree/mod.rs":"981e7b47cdf19fd223416f0f853e13f243fc32a5567ec38ad393b5210d498652","src/tree/root.rs":"a6d6531489471bf555174eeefcac553466ead52490043e20576b9e4e9f03c825","src/tree/tests.rs":"1527eda193b5bbc226ee2544d8b68e5712e959fb70f4cd989dfc7ebcf54ba034","src/unit/bytes.rs":"97423209a1e10aafa5b40ed11af762e0f61dbbaf08643e96a50d99767105c536","src/unit/display.rs":"88a5ee5ce3136a417087fb62113f74d5fec55d9927ac275b1d382bc948795bdd","src/unit/duration.rs":"a036d01483853e35ffced88d93957da8575db9dc0b6fc2cc33b67019b979975d","src/unit/human.rs":"684f3dcac14140eea2b701408dd052d4db081e69293b2bb7bfeafa77dd5769d9","src/unit/mod.rs":"f317c8464aa9847dba6e6f75ed001d0ed43f014f1c4255b23c6513c5a37d494e","src/unit/range.rs":"b2c6d0fd85b960bc845fb496551103cbd0338a6313a5f49bdffbc0c78d58a26f","src/unit/traits.rs":"8556583a0a6ea4863e9c5c94808650db33b60e3ec55085d6872e026729c9aa9e"},"package":"d73c6b64cb5b99eb63ca97d378685712617ec0172ff5c04cd47a489d3e2c51f8"} \ No newline at end of file
diff --git a/vendor/prodash/CHANGELOG.md b/vendor/prodash/CHANGELOG.md
new file mode 100644
index 000000000..58912658e
--- /dev/null
+++ b/vendor/prodash/CHANGELOG.md
@@ -0,0 +1,2593 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## 23.1.1 (2023-03-02)
+
+A maintenance release without user-facing changes.
+
+Most notably, `parking_lot` was upgraded to the latest version.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 2 commits contributed to the release.
+ - 2 days passed between releases.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Prepare changelog ([`1a4eb9b`](https://github.com/byron/prodash/commit/1a4eb9b5dea3e100b188be956ab7670f0b8d5ad6))
+ - Upgrade dependencies, particularly `parking_lot` ([`7ae8a07`](https://github.com/byron/prodash/commit/7ae8a0793752b713c6605be45688ca81fbb7e75e))
+</details>
+
+## 23.1.0 (2023-02-28)
+
+### New Features
+
+ - <csr-id-6f966b4f859f1b02775dcb3461bacf46b46ab707/> improve performance of `progress::tree` operations by more than 50%.
+ This was done by implementing shared state in a simple Mutex protected hashmap
+ which for typical programs with less contention is faster than using the `dashmap`
+ crate.
+
+ However, for those who know they need it, the previous implementation is still available
+ in with the `progress-tree-hp-hashmap` feature toggle.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 4 commits contributed to the release.
+ - 61 days passed between releases.
+ - 1 commit was understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Release prodash v23.1.0 ([`45b4b7e`](https://github.com/byron/prodash/commit/45b4b7ea06b08cb30bd1f1ca2c05874bb0cf19ce))
+ - No need for `unsound-local-offset` anymore. ([`c53ab9d`](https://github.com/byron/prodash/commit/c53ab9d5d238be4aaabfb8a990c80ca1a7c58dec))
+ - Make fmt ([`490336f`](https://github.com/byron/prodash/commit/490336f9920ccde0ebe8d142dc51b390e9afc899))
+ - Improve performance of `progress::tree` operations by more than 50%. ([`6f966b4`](https://github.com/byron/prodash/commit/6f966b4f859f1b02775dcb3461bacf46b46ab707))
+</details>
+
+## 23.0.0 (2022-12-29)
+
+### New Features (BREAKING)
+
+ - <csr-id-a1db1b27fc7f14be052bbfc660c9c9b174c1d3cc/> Implement `Hash` for `Task` to avoid redrawing if nothing changes with the Line renderer.
+ That way, if everything stops due to a user prompt, the user's input won't be clobbered
+ continnuously.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 2 commits contributed to the release.
+ - 23 days passed between releases.
+ - 1 commit was understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Release prodash v23.0.0 ([`d527e4b`](https://github.com/byron/prodash/commit/d527e4be160ac80714deeaca11b8ed677f8d842f))
+ - Implement `Hash` for `Task` to avoid redrawing if nothing changes with the Line renderer. ([`a1db1b2`](https://github.com/byron/prodash/commit/a1db1b27fc7f14be052bbfc660c9c9b174c1d3cc))
+</details>
+
+## 22.1.0 (2022-12-06)
+
+### New Features
+
+ - <csr-id-ea9aa5815ef2d1c734b85c185ddb65ac731b1195/> `progress::Key` now supports 6 levels of hierarchy instead of 4.
+ That way it's less likely that surprises occour of more than necessary
+ levels are added.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 2 commits contributed to the release.
+ - 1 day passed between releases.
+ - 1 commit was understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Release prodash v22.1.0 ([`2ae17c5`](https://github.com/byron/prodash/commit/2ae17c5bd7ab91b9e05a16fb4f771ce0ce0001f4))
+ - `progress::Key` now supports 6 levels of hierarchy instead of 4. ([`ea9aa58`](https://github.com/byron/prodash/commit/ea9aa5815ef2d1c734b85c185ddb65ac731b1195))
+</details>
+
+## 22.0.0 (2022-12-05)
+
+<csr-id-46aeffd13cda49146c8a33e93c8c9b0fbcb15c8b/>
+
+### Chore
+
+ - <csr-id-46aeffd13cda49146c8a33e93c8c9b0fbcb15c8b/> switch to Rust edition 2021
+
+### Changed (BREAKING)
+
+ - <csr-id-53cb09dc0314b0e8ce58dc50e0c07a053b963ccd/> remove `Tree` and `TreeOptions` in favor of `tree::Root` and `tree::root::Options`.
+ Previously it was confusing what a tree Root actually is due to the
+ rename, and ambiguity isn't what we would want here.
+
+### New Features (BREAKING)
+
+ - <csr-id-edab37364276864d45241c2946173388a0602f23/> `From<tree::root::Options> for tree::Root`, `tree::root::Options::create()` returns `tree::Root` instead of `Arc`.
+ That way we won't be forced to produce an `Arc` if it's not needed.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 5 commits contributed to the release.
+ - 11 days passed between releases.
+ - 3 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Release prodash v22.0.0 ([`8b78fe9`](https://github.com/byron/prodash/commit/8b78fe9614a584096f4fefed38a8965266caf2e9))
+ - Switch to Rust edition 2021 ([`46aeffd`](https://github.com/byron/prodash/commit/46aeffd13cda49146c8a33e93c8c9b0fbcb15c8b))
+ - Make fmt ([`6e90cb9`](https://github.com/byron/prodash/commit/6e90cb965377762920d4dbb5debe8d47a4be89a2))
+ - `From<tree::root::Options> for tree::Root`, `tree::root::Options::create()` returns `tree::Root` instead of `Arc`. ([`edab373`](https://github.com/byron/prodash/commit/edab37364276864d45241c2946173388a0602f23))
+ - Remove `Tree` and `TreeOptions` in favor of `tree::Root` and `tree::root::Options`. ([`53cb09d`](https://github.com/byron/prodash/commit/53cb09dc0314b0e8ce58dc50e0c07a053b963ccd))
+</details>
+
+## 21.1.0 (2022-11-23)
+
+### New Features
+
+ - <csr-id-c332a6f266a6ae0cacf19cb523e551bb63c1e7ea/> identify each progress item with `Id` using `add_child_with_id()`.
+ An `Id` is four bytes like b"TREE" that are stable and
+ identify progress items (as created by `add_child(…)` within
+ a function call.
+
+ Callers may use this knowledge to pick specific progress items
+ for consumption, instead of trying to rely on identifying tasks
+ by name which may change.
+
+ The identifier can also be queried with `Progress::id()`, even
+ though it could be `prodash::progress::UNKNOWN` if the progress
+ item wasn't created with `add_child_with_id()`.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 4 commits contributed to the release.
+ - 37 days passed between releases.
+ - 1 commit was understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Release prodash v21.1.0 ([`6327e15`](https://github.com/byron/prodash/commit/6327e1587d6886f4279ec037ad587dab52a7cedf))
+ - `make fmt` ([`5415acc`](https://github.com/byron/prodash/commit/5415acc4cae7bab81a22758fe969bdb8b3deac13))
+ - Make it easy to format 'use'es in the codebase. ([`5b8e54d`](https://github.com/byron/prodash/commit/5b8e54d959f895d762a4a77d194f3947a6302783))
+ - Identify each progress item with `Id` using `add_child_with_id()`. ([`c332a6f`](https://github.com/byron/prodash/commit/c332a6f266a6ae0cacf19cb523e551bb63c1e7ea))
+</details>
+
+## 21.0.0 (2022-10-17)
+
+### New Features
+
+ - <csr-id-3347a0294c5b95270c34de9f396214353baea36d/> `impl Progress for &mut T: where T: Progress`.
+ This makes it possible to hand borrowed progress implementations to
+ functions that need progress reporting, making the usage of progress
+ easier.
+
+### New Features (BREAKING)
+
+ - <csr-id-300181bdd4b2ef1822dddd1fe814d7e3e5b26779/> remove `Progress: 'static` requirement.
+ This requirement can be added where used and where needed, and
+ originally snuck in because it was easier and `Progress` implementations
+ typically are `'static` as well.
+
+ However, that requirement made it impossible to implement `Progoress`
+ for `&mut T where T: Progress`.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 3 commits contributed to the release.
+ - 27 days passed between releases.
+ - 2 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Release prodash v21.0.0 ([`d7fcf5b`](https://github.com/byron/prodash/commit/d7fcf5bb5a6bad8225c22ce6df848835f5790847))
+ - `impl Progress for &mut T: where T: Progress`. ([`3347a02`](https://github.com/byron/prodash/commit/3347a0294c5b95270c34de9f396214353baea36d))
+ - Remove `Progress: 'static` requirement. ([`300181b`](https://github.com/byron/prodash/commit/300181bdd4b2ef1822dddd1fe814d7e3e5b26779))
+</details>
+
+## 20.2.0 (2022-09-20)
+
+### New Features
+
+ - <csr-id-4904065a51594b5bc4f32a247e513b45093373fa/> Add `Progress::set_max()` to set the highgest expected progress value.
+
+### Bug Fixes
+
+ - <csr-id-414bc71e79475335345dbc529566a6efc3afee23/> don't reset the shared counter value on `init`.
+ It's possible to re-initialize the progress, and when that's done
+ it would detach the counter from previous instances that might have
+ been observed by callers to `counter()`, which is surprising.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 4 commits contributed to the release.
+ - 2 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Release prodash v20.2.0 ([`f49f470`](https://github.com/byron/prodash/commit/f49f4703f5d09f9bbe8ec32bd249c42bd129d7ea))
+ - Don't reset the shared counter value on `init`. ([`414bc71`](https://github.com/byron/prodash/commit/414bc71e79475335345dbc529566a6efc3afee23))
+ - Add `Progress::set_max()` to set the highgest expected progress value. ([`4904065`](https://github.com/byron/prodash/commit/4904065a51594b5bc4f32a247e513b45093373fa))
+ - Make more explicit what is cloned (without altering actual behaviour) ([`b023efd`](https://github.com/byron/prodash/commit/b023efdc9c076f15cef1978ad95d932f782bdea7))
+</details>
+
+## 20.1.1 (2022-09-20)
+
+### Bug Fixes
+
+ - <csr-id-a0e7da7d08331eeeea8ca0cb6e349fe32ce876bc/> implement `Progress::counter()` for all utility types.
+ This was forgotten previously as there was a default implementation
+ right from the start.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 2 commits contributed to the release.
+ - 1 commit was understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Release prodash v20.1.1 ([`d152701`](https://github.com/byron/prodash/commit/d1527018e5156921c3263c9d9afafad8c488a3b9))
+ - Implement `Progress::counter()` for all utility types. ([`a0e7da7`](https://github.com/byron/prodash/commit/a0e7da7d08331eeeea8ca0cb6e349fe32ce876bc))
+</details>
+
+## 20.1.0 (2022-09-20)
+
+### New Features
+
+ - <csr-id-08be317dbd77f0fdb9673b9c24c239ad0d5078c3/> `Progress::counter()` returns a shared step counter.
+ This is useful if multiple threads want to access the same progress, without the need
+ for provide each their own progress and aggregating the result.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 4 commits contributed to the release.
+ - 5 days passed between releases.
+ - 1 commit was understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Thanks Clippy
+
+<csr-read-only-do-not-edit/>
+
+[Clippy](https://github.com/rust-lang/rust-clippy) helped 1 time to make code idiomatic.
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Release prodash v20.1.0 ([`6591872`](https://github.com/byron/prodash/commit/65918722da151b756f6a3facc3577bd2002feadc))
+ - Fix compile warnings for some configurations ([`fe0ac06`](https://github.com/byron/prodash/commit/fe0ac0659c5faba9b9c0b699850ffc9e66b566f0))
+ - Thanks clippy ([`deb7791`](https://github.com/byron/prodash/commit/deb77916c1ced591410aba2593284c6a1345d426))
+ - `Progress::counter()` returns a shared step counter. ([`08be317`](https://github.com/byron/prodash/commit/08be317dbd77f0fdb9673b9c24c239ad0d5078c3))
+</details>
+
+## 20.0.1 (2022-09-15)
+
+### Bug Fixes
+
+ - <csr-id-80a4850802023e5a0f6fe85e6af416aaeb729e21/> Allow builds to succeed on Windows by not registering SIGWINCH signal.
+ Without said signal, the render line will not automatically resize
+ anymore, which in theory can be compensated for by re-obtaining
+ the terminal dimensions every now and then (currently not done).
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 5 commits contributed to the release.
+ - 2 days passed between releases.
+ - 1 commit was understood as [conventional](https://www.conventionalcommits.org).
+ - 1 unique issue was worked on: [#12](https://github.com/byron/prodash/issues/12)
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **[#12](https://github.com/byron/prodash/issues/12)**
+ - Allow builds to succeed on Windows by not registering SIGWINCH signal. ([`80a4850`](https://github.com/byron/prodash/commit/80a4850802023e5a0f6fe85e6af416aaeb729e21))
+ - Only register SIGWINCH signal on unix ([`5de765b`](https://github.com/byron/prodash/commit/5de765b6c134be6c439583dd89297fe2b3f1e41f))
+ - Adjust CI configuration to catch more cross-platform errors ([`b7e0f2c`](https://github.com/byron/prodash/commit/b7e0f2c97bc6c384dfbb5ed390909a5a6850bb74))
+ * **Uncategorized**
+ - Release prodash v20.0.1 ([`b98595b`](https://github.com/byron/prodash/commit/b98595b8cc9fd2f0bf3ea8cc22fbd7cf6cb07ab1))
+ - Do away with unsafe code by using safe wrappers instead. ([`447aa0f`](https://github.com/byron/prodash/commit/447aa0f518aaa97dd061813a501e6a0b8512dd88))
+</details>
+
+## 20.0.0 (2022-09-12)
+
+<csr-id-a3b26782dc074c469b5fc480595d2ac9ef8bc9d0/>
+
+### New Features
+
+ - <csr-id-bab2ea09089e2eb8e4e826bb17211a444aa35f31/> line renderer adjusts when resizing the terminal.
+
+### Chore (BREAKING)
+
+ - <csr-id-a3b26782dc074c469b5fc480595d2ac9ef8bc9d0/> upgrade dependencies to tui `0.19` and crossterm `0.25`
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 3 commits contributed to the release.
+ - 175 days passed between releases.
+ - 2 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Release prodash v20.0.0 ([`54e0a6a`](https://github.com/byron/prodash/commit/54e0a6ae39ff53b5c6e7abe9f44d003b833bfed6))
+ - Line renderer adjusts when resizing the terminal. ([`bab2ea0`](https://github.com/byron/prodash/commit/bab2ea09089e2eb8e4e826bb17211a444aa35f31))
+ - Upgrade dependencies to tui `0.19` and crossterm `0.25` ([`a3b2678`](https://github.com/byron/prodash/commit/a3b26782dc074c469b5fc480595d2ac9ef8bc9d0))
+</details>
+
+## 19.0.1 (2022-03-20)
+
+### Bug Fixes
+
+ - <csr-id-dbca35f478253176e852c177e79b3253eaafe3bd/> line renderer will clear previous lines if progress is lost
+ Previously it would just exit its main loop and leave lines on screen.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 3 commits contributed to the release.
+ - 1 commit was understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Release prodash v19.0.1 ([`a0c88b7`](https://github.com/byron/prodash/commit/a0c88b7ce0541edd9b6f677454bc0fe694b16fc7))
+ - Line renderer will clear previous lines if progress is lost ([`dbca35f`](https://github.com/byron/prodash/commit/dbca35f478253176e852c177e79b3253eaafe3bd))
+ - Fix benchmark compilation ([`39bc237`](https://github.com/byron/prodash/commit/39bc2379c966244746b4ce361e576997159e19aa))
+</details>
+
+## 19.0.0 (2022-03-20)
+
+### New Features
+
+ - <csr-id-cba841c828142c0dd028dd9413c31f509f2bbb1b/> Improve render-log performance greatly.
+ Previously it would check the current time each time somebody
+ wants to log on any logger, greatly reducing performance as
+ it would block on the mutex rust-std uses internally.
+
+ Now we use a single thread to provide information about whether or not
+ we may log, whose lifetime is bound to all of the log instances it
+ governs.
+
+### New Features (BREAKING)
+
+ - <csr-id-b6d5245344bde92672cd98aecacb5d94ecca4e19/> Allow rendererers to respond to dropped progress roots
+ Previously it needed extra effort to communicate that a the computation
+ was done and the renderer should stop rendering progress.
+
+ Now they only obtain a weak progress instance so it can drop if the
+ computation is done, terminating it naturally and in time.
+
+ Note that in case of the TUI, it would still be needed to respond
+ to the GUI having shut down due to user request.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 4 commits contributed to the release over the course of 41 calendar days.
+ - 41 days passed between releases.
+ - 2 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Release prodash v19.0.0 ([`fda577e`](https://github.com/byron/prodash/commit/fda577e72d831d455289a6786262ea94f861063e))
+ - Improve render-log performance greatly. ([`cba841c`](https://github.com/byron/prodash/commit/cba841c828142c0dd028dd9413c31f509f2bbb1b))
+ - Allow rendererers to respond to dropped progress roots ([`b6d5245`](https://github.com/byron/prodash/commit/b6d5245344bde92672cd98aecacb5d94ecca4e19))
+ - Actually, the correct dashmap version is 5.1 ([`6bdb7e8`](https://github.com/byron/prodash/commit/6bdb7e8ea3a65d51e69436799de3f9862b55dba4))
+</details>
+
+## 18.0.2 (2022-02-07)
+
+<csr-id-e4f2ab842b34f4a4fe9b2f4c34b664a2e3dba200/>
+
+### Chore
+
+ - <csr-id-e4f2ab842b34f4a4fe9b2f4c34b664a2e3dba200/> Upgrade dashmap to 5.0.1 (with security fix)
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 2 commits contributed to the release.
+ - 5 days passed between releases.
+ - 1 commit was understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Release prodash v18.0.2 ([`69f4295`](https://github.com/byron/prodash/commit/69f42953ec69da4c2c34c8c137dfe7b7a1c12598))
+ - Upgrade dashmap to 5.0.1 (with security fix) ([`e4f2ab8`](https://github.com/byron/prodash/commit/e4f2ab842b34f4a4fe9b2f4c34b664a2e3dba200))
+</details>
+
+## 18.0.1 (2022-02-01)
+
+### Bug Fixes
+
+ - <csr-id-a1f8aa650d1a1d2ac53025e29c71782b1cab58c5/> Downgrade to dashmap 4.0
+ While waiting for unoundness to be resolved.
+
+ See the issue for details: https://github.com/xacrimon/dashmap/issues/167
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 2 commits contributed to the release.
+ - 9 days passed between releases.
+ - 1 commit was understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Release prodash v18.0.1 ([`e9769dd`](https://github.com/byron/prodash/commit/e9769dd7ad5c6f4618dd138aea74aba10fba69b3))
+ - Downgrade to dashmap 4.0 ([`a1f8aa6`](https://github.com/byron/prodash/commit/a1f8aa650d1a1d2ac53025e29c71782b1cab58c5))
+</details>
+
+## 18.0.0 (2022-01-23)
+
+### New Features (BREAKING)
+
+ - <csr-id-482b54f9c584b0e2d22c53622c9f7ad45b79ad2c/> upgrade to tui 0.17
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 3 commits contributed to the release.
+ - 19 days passed between releases.
+ - 1 commit was understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Release prodash v18.0.0 ([`10531ae`](https://github.com/byron/prodash/commit/10531ae35570af6003275a92ba40612a5e23678f))
+ - Prepare changelog ([`a7a58c0`](https://github.com/byron/prodash/commit/a7a58c0fb6ea1068b914a653eb33c96dc157dc06))
+ - Upgrade to tui 0.17 ([`482b54f`](https://github.com/byron/prodash/commit/482b54f9c584b0e2d22c53622c9f7ad45b79ad2c))
+</details>
+
+## 17.0.0 (2022-01-03)
+
+### New Features (BREAKING)
+
+ - <csr-id-46214a306793a6a9f304f854dfd7396ceaf433d3/> Add `MessageLevel` parameter to `Progress::show_throughput_with(…, level)`
+ This allows to use message level for highlighting of certain
+ throughputs and results.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 2 commits contributed to the release.
+ - 1 commit was understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Release prodash v17.0.0 ([`9085aaa`](https://github.com/byron/prodash/commit/9085aaaa0a7844c1d4b4afcec4b688f5c398ba87))
+ - Add `MessageLevel` parameter to `Progress::show_throughput_with(…, level)` ([`46214a3`](https://github.com/byron/prodash/commit/46214a306793a6a9f304f854dfd7396ceaf433d3))
+</details>
+
+## 16.1.3 (2022-01-03)
+
+### Bug Fixes
+
+ - <csr-id-2fe3eebbd62ddd9beacc294eb6ec04b4b39a26f6/> `Progress::init(None, None)` now resets the progress entirely
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 2 commits contributed to the release.
+ - 2 days passed between releases.
+ - 1 commit was understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Release prodash v16.1.3 ([`6551d45`](https://github.com/byron/prodash/commit/6551d45837aecc853f78e848b4257b559287a6ac))
+ - `Progress::init(None, None)` now resets the progress entirely ([`2fe3eeb`](https://github.com/byron/prodash/commit/2fe3eebbd62ddd9beacc294eb6ec04b4b39a26f6))
+</details>
+
+## 16.1.2 (2022-01-01)
+
+### Bug Fixes
+
+ - <csr-id-aa70a27ef1de930fdd00266239ee262b52a681a1/> reset the shared value on init to avoid keeping the previously set value.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 2 commits contributed to the release.
+ - 4 days passed between releases.
+ - 1 commit was understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Release prodash v16.1.2 ([`3f16e6e`](https://github.com/byron/prodash/commit/3f16e6e31ee2b6df70841900d5b118f6533f9d2e))
+ - Reset the shared value on init to avoid keeping the previously set value. ([`aa70a27`](https://github.com/byron/prodash/commit/aa70a27ef1de930fdd00266239ee262b52a681a1))
+</details>
+
+## 16.1.1 (2021-12-27)
+
+### Bug Fixes
+
+ - <csr-id-ca5f544594facc92c8744b293c7287dcffe065e5/> correct signature of new 'running()' method
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 2 commits contributed to the release.
+ - 1 commit was understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Release prodash v16.1.1 ([`f426c20`](https://github.com/byron/prodash/commit/f426c20dca5440f6d39c979a8a851ee444a2c388))
+ - Correct signature of new 'running()' method ([`ca5f544`](https://github.com/byron/prodash/commit/ca5f544594facc92c8744b293c7287dcffe065e5))
+</details>
+
+## 16.1.0 (2021-12-27)
+
+### New Features
+
+ - <csr-id-3886754817ac528178b8ea326d0b4d576168eb28/> Setting the progress value is now 9x faster
+ This is accomplished at the cost of not autoamtically setting the
+ progress to 'running' anymore when the progress is set.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 3 commits contributed to the release.
+ - 8 days passed between releases.
+ - 1 commit was understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Release prodash v16.1.0 ([`34ae001`](https://github.com/byron/prodash/commit/34ae0014627c5fde785691ffc6583c52b629da51))
+ - Setting the progress value is now 9x faster ([`3886754`](https://github.com/byron/prodash/commit/3886754817ac528178b8ea326d0b4d576168eb28))
+ - An experiment to show we don't want to rely on dashmap for this ([`4f527c1`](https://github.com/byron/prodash/commit/4f527c12caa85018de293762194f9a2aed5daaea))
+</details>
+
+## 16.0.1 (2021-12-19)
+
+<csr-id-e6f53d59ef1aef027a2aad5b164535c6ca0d620b/>
+
+### Chore
+
+ - <csr-id-e6f53d59ef1aef027a2aad5b164535c6ca0d620b/> upgrade dashmap to latest version
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 6 commits contributed to the release over the course of 47 calendar days.
+ - 109 days passed between releases.
+ - 1 commit was understood as [conventional](https://www.conventionalcommits.org).
+ - 1 unique issue was worked on: [#8](https://github.com/byron/prodash/issues/8)
+
+### Thanks Clippy
+
+<csr-read-only-do-not-edit/>
+
+[Clippy](https://github.com/rust-lang/rust-clippy) helped 1 time to make code idiomatic.
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **[#8](https://github.com/byron/prodash/issues/8)**
+ - Run `cargo changelog --write` for an improved changelog ([`a1054e8`](https://github.com/byron/prodash/commit/a1054e89b6f5bbdead1a5c2f975cdce0da0700e7))
+ * **Uncategorized**
+ - Release prodash v16.0.1 ([`9418df2`](https://github.com/byron/prodash/commit/9418df2660e5cb17c9906f86fb379c0d22c7ddb7))
+ - Upgrade dashmap to latest version ([`e6f53d5`](https://github.com/byron/prodash/commit/e6f53d59ef1aef027a2aad5b164535c6ca0d620b))
+ - Cleanup changelog ([`5aa6275`](https://github.com/byron/prodash/commit/5aa627523536a85c382f0da20636963387b437bd))
+ - Thanks clippy ([`c1258e2`](https://github.com/byron/prodash/commit/c1258e250207889c62ef3208590d84185752e1a2))
+ - Looks like array syntax isn't supported anymore ([`bfbce01`](https://github.com/byron/prodash/commit/bfbce01af9b90f5ae6b0490d6d7cdc29e05a1338))
+</details>
+
+## v16.0.0 (2021-08-31)
+
+### Improvements
+
+- Use `time` version 0.3 when the `local-time` feature is enabled
+
+### Breaking
+
+- rename cargo feature `localtime` to `local-time`
+- The `local-time` feature is not the default anymore, enable it using the `RUSTFLAGS="--cfg unsound_local_offset"` environment when building the binary.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 4 commits contributed to the release.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Release prodash v16.0.0 ([`fec0304`](https://github.com/byron/prodash/commit/fec0304eba34db15c981a040c21e0371a5ac50d2))
+ - Improve docs ([`71d66ec`](https://github.com/byron/prodash/commit/71d66ecd2b3330759890f9056ea686c269fbe63a))
+ - Upgrade to time 0.3 and opt in to unsound features for example binaries (unix only) ([`fb3e0b0`](https://github.com/byron/prodash/commit/fb3e0b035cbe911d72a544a779d46cdda7b8105c))
+ - Rename 'localtime' to 'local-time' (cargo feature)… ([`b6ee809`](https://github.com/byron/prodash/commit/b6ee8096d22ce7a97c9bce9bcddad966a05b365f))
+</details>
+
+## v15.0.1 (2021-08-31)
+
+* crosstermion is optional for some renderers (again)
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 2 commits contributed to the release.
+ - 25 days passed between releases.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Release prodash v15.0.1 ([`72ea8a9`](https://github.com/byron/prodash/commit/72ea8a9565da457d6f1881bc64e6223c57a951a0))
+ - Fix manifest key! ([`d9275f2`](https://github.com/byron/prodash/commit/d9275f22c3845fb21f601d5d426a0949a398c47d))
+</details>
+
+## v15.0.0 (2021-08-05)
+
+* Upgrade to TUI v0.16
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 4 commits contributed to the release over the course of 32 calendar days.
+ - 50 days passed between releases.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Thanks Clippy
+
+<csr-read-only-do-not-edit/>
+
+[Clippy](https://github.com/rust-lang/rust-clippy) helped 1 time to make code idiomatic.
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Upgrade to tui 0.16 ([`7b45023`](https://github.com/byron/prodash/commit/7b45023eb724429b2e0ba8794d7e80f867b51456))
+ - Thanks clippy ([`5ebf8b4`](https://github.com/byron/prodash/commit/5ebf8b4af0c19472a0efb29a2ac95dbe47c3fdd8))
+ - Dependency update ([`30d61c5`](https://github.com/byron/prodash/commit/30d61c5f57ab9d1200d237a23d272a43f1609956))
+ - Use pin instead of boxing unnecessarily. ([`aa715ae`](https://github.com/byron/prodash/commit/aa715ae0903cbc95a0e1245e5fa62e3b505eae35))
+</details>
+
+## v14.0.0 (2021-06-16)
+
+* Swap `ctrlc` crate with `signal-hook` which is a must in library crates. `ctrlc` is only for applications
+ who can control the single handler that it installs entirely.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 3 commits contributed to the release.
+ - 38 days passed between releases.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - (cargo-release) version 14.0.0 ([`a6492c6`](https://github.com/byron/prodash/commit/a6492c697f6afa79ee9bdec7c355d7f4388e5a6c))
+ - Prepare release ([`c2e3193`](https://github.com/byron/prodash/commit/c2e3193dab419c5bc94ee6138430248c0d36e299))
+ - Use signal-hook instead of ctrlc ([`a733267`](https://github.com/byron/prodash/commit/a7332677625fecf18566a05498b5e9bbd2108bc6))
+</details>
+
+## v13.1.1 (2021-05-08)
+
+* Fix compile error (and protect from that regression) if `render-line-autoconfigure` was enabled.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 2 commits contributed to the release.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - (cargo-release) version 13.1.1 ([`d120f2f`](https://github.com/byron/prodash/commit/d120f2fb4f46fc66c4533d5c4f5f4ee1aeec7fd4))
+ - Fix compile issue, prep for patch release ([`584768a`](https://github.com/byron/prodash/commit/584768a07b37d6c11928dc022c44cf2d5c2c7e08))
+</details>
+
+## v13.1.0 (2021-05-08)
+
+* With the `render-line-autoconfigure` feature toggle, the new
+ `Options::default().auto_configure(…)` method allows to adapt to the terminal/non-terminal autmatically.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 3 commits contributed to the release.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - (cargo-release) version 13.1.0 ([`8aedcab`](https://github.com/byron/prodash/commit/8aedcab872bfbad27c4ca120370e428d1a227324))
+ - Update changelog ([`ffd4a1f`](https://github.com/byron/prodash/commit/ffd4a1f15a4ca0872f535fc2460890a9328ba7fd))
+ - Add render-line-autoconfigure feature toggle ([`212ce36`](https://github.com/byron/prodash/commit/212ce369c290a14e99be91e7d5cafb154154cf8b))
+</details>
+
+## v13.0.1 (2021-05-08)
+
+* The line renderer won't try to hide the cursor if the output isn't a terminal.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 6 commits contributed to the release over the course of 5 calendar days.
+ - 5 days passed between releases.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Thanks Clippy
+
+<csr-read-only-do-not-edit/>
+
+[Clippy](https://github.com/rust-lang/rust-clippy) helped 1 time to make code idiomatic.
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - (cargo-release) version 13.0.1 ([`0d41f11`](https://github.com/byron/prodash/commit/0d41f111875598a90b7a2a7a8f4e0872feb1a285))
+ - Prepare point release ([`8bfd42b`](https://github.com/byron/prodash/commit/8bfd42b0b03fc2b108ec68ea925e3d27742789da))
+ - Don't try to hide the cursor if the output isn't a terminal ([`26c7497`](https://github.com/byron/prodash/commit/26c74976ee4e6c3eec89dddf1a90e6ce22539dd8))
+ - More robust example for handling ttys and TUI ([`4d1dd31`](https://github.com/byron/prodash/commit/4d1dd313430054f17b2daddad8bd9d81061e60b0))
+ - Thanks clippy ([`13404f5`](https://github.com/byron/prodash/commit/13404f5f34d49e5607fa77e56bef0669b24c4adb))
+ - Remove crosstermion, it now lives in https://github.com/Byron/tui-crates ([`f560f84`](https://github.com/byron/prodash/commit/f560f84436c1bd2b5af8b0af44e8e86200d22cc2))
+</details>
+
+## v13.0.0 (2021-05-02)
+
+<csr-id-e3665a2100fba190fc0f047ff05f2904f4dcaf4a/>
+<csr-id-c91d410e8d6242b78c44119155b5fc3b2956d111/>
+<csr-id-03d1c2067778fb6ec231bf18dd587046a03434bc/>
+
+* Upgrade to TUI v0.15
+
+### Other
+
+ - <csr-id-e3665a2100fba190fc0f047ff05f2904f4dcaf4a/> prep release
+ - <csr-id-c91d410e8d6242b78c44119155b5fc3b2956d111/> prepare release
+ - <csr-id-03d1c2067778fb6ec231bf18dd587046a03434bc/> Upgrade to tui 0.15
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 13 commits contributed to the release over the course of 109 calendar days.
+ - 110 days passed between releases.
+ - 3 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - (cargo-release) version 13.0.0 ([`c0a97fa`](https://github.com/byron/prodash/commit/c0a97fa648cb2a0491d09bd0fbdd1daa6d6e290e))
+ - Prep release ([`e3665a2`](https://github.com/byron/prodash/commit/e3665a2100fba190fc0f047ff05f2904f4dcaf4a))
+ - Upgrade to tui 0.15 ([`edd11be`](https://github.com/byron/prodash/commit/edd11be0aa6ed84370603452286182a28c577961))
+ - Prepare release ([`c91d410`](https://github.com/byron/prodash/commit/c91d410e8d6242b78c44119155b5fc3b2956d111))
+ - Upgrade to tui 0.15 ([`03d1c20`](https://github.com/byron/prodash/commit/03d1c2067778fb6ec231bf18dd587046a03434bc))
+ - Revert "Allow most recent version of 'time' crate" ([`ee8ab91`](https://github.com/byron/prodash/commit/ee8ab91f74d6e598f59947d08405ca1f7fdb2785))
+ - Allow most recent version of 'time' crate ([`300aa7b`](https://github.com/byron/prodash/commit/300aa7b29ff6bad31197368340bebd2e2a5dfea0))
+ - Run actions on main ([`a2b3037`](https://github.com/byron/prodash/commit/a2b303730602c1b6e4a63475b134f87d640b6d2f))
+ - Make it more obvious what prodash actually is ([`5b862d5`](https://github.com/byron/prodash/commit/5b862d5e262cc08466afed235b862a6e79bffc8b))
+ - Fix compile warning ([`d48f3b9`](https://github.com/byron/prodash/commit/d48f3b97643cbd3a264400cacff29eb1436cb002))
+ - Use new resolver ([`ad03a43`](https://github.com/byron/prodash/commit/ad03a43056351899b8b76a62b0a0598b83c1e213))
+ - Fix compile warnings ([`4cb8681`](https://github.com/byron/prodash/commit/4cb8681f3826994d92b703cc7cf105ccf01cc4d8))
+ - Fix typo ([`75f311e`](https://github.com/byron/prodash/commit/75f311e5da2b5aeff608048d13075acb3dd41a0e))
+</details>
+
+## v12.0.2 (2021-01-12)
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 2 commits contributed to the release.
+ - 3 days passed between releases.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - (cargo-release) version 12.0.2 ([`29e16a4`](https://github.com/byron/prodash/commit/29e16a4faa4293f160b3294d327d2f7c4083ca2c))
+ - Add all missing docs to prodash ([`473c560`](https://github.com/byron/prodash/commit/473c56048e762ca9976260598005e1508e8d579c))
+</details>
+
+## v12.0.1 (2021-01-08)
+
+* upgrade dependencies
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 10 commits contributed to the release.
+ - 4 days passed between releases.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Thanks Clippy
+
+<csr-read-only-do-not-edit/>
+
+[Clippy](https://github.com/rust-lang/rust-clippy) helped 2 times to make code idiomatic.
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - (cargo-release) version 12.0.1 ([`e93d001`](https://github.com/byron/prodash/commit/e93d00128cf24f63dcc18279bedea81d88732ef4))
+ - Prepare release ([`3e58d82`](https://github.com/byron/prodash/commit/3e58d826b855f6f10b7a787d34288f84eea35e5f))
+ - Fix localtime support ([`a3298d5`](https://github.com/byron/prodash/commit/a3298d5ab7e190d24ecdddc78f8c1cfcf5226bcb))
+ - Fix time offset calculations which can (and do) indeed fail ([`fed01f7`](https://github.com/byron/prodash/commit/fed01f7bccc234534631341706527a47d19a6dfa))
+ - Remove previous executor/reactor in favor of async-executor ([`1bea3cb`](https://github.com/byron/prodash/commit/1bea3cb16136c59bf9654fff16bcfefb1d0fa615))
+ - Use new spawn for simple example ([`9a8af0f`](https://github.com/byron/prodash/commit/9a8af0fe86246cbc99bd8440529975189e77cc00))
+ - Upgrade 'rand' ([`1c4930a`](https://github.com/byron/prodash/commit/1c4930af7e8960a94360f2ac6fdeae48920f7f93))
+ - Thanks clippy ([`644809a`](https://github.com/byron/prodash/commit/644809a7e40fc3b49116e0dfad718fc4fa850164))
+ - Upgrade dashmap and env_logger ([`cec8ab3`](https://github.com/byron/prodash/commit/cec8ab38513db79e588dc633218c565d5634f23f))
+ - Thanks clippy ([`32be130`](https://github.com/byron/prodash/commit/32be1300186fce4d543861b850ca7adf7d8c76e2))
+</details>
+
+## v12.0.0 (2021-01-04)
+
+* Upgrade to TUI v0.14
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 2 commits contributed to the release.
+ - 49 days passed between releases.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Upgrade to tui 14 ([`06791b3`](https://github.com/byron/prodash/commit/06791b3cf0dd2589985bc1deb82b73e9278ae723))
+ - Update to tui 14 ([`169d62d`](https://github.com/byron/prodash/commit/169d62d04f50eb1b97e6766eb8e7a8f7878b2aef))
+</details>
+
+## v11.0.0 (2020-11-15)
+
+* Upgrade to TUI v0.13 and crossterm v0.18
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 10 commits contributed to the release over the course of 47 calendar days.
+ - 59 days passed between releases.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Bump major version ([`b2989fa`](https://github.com/byron/prodash/commit/b2989faa799f27701193c52c1b335302f9751639))
+ - Upgrade to tui v0.13 ([`808f7d5`](https://github.com/byron/prodash/commit/808f7d5983ed9a3165afbd8f59959eca16d4a16a))
+ - Crosstermion now uses tui v0.13 ([`7684a50`](https://github.com/byron/prodash/commit/7684a50e1b94d6f19fa7d2c4d646ff569a51ffbd))
+ - Cargo clippy ([`725449b`](https://github.com/byron/prodash/commit/725449bfb3f4e39a543b023561b20365d9052a62))
+ - Update README.md ([`b485568`](https://github.com/byron/prodash/commit/b4855683b4d33c4db2eade4db7a33a53de47961a))
+ - Update README with accurate instructions on running example ([`d9288e9`](https://github.com/byron/prodash/commit/d9288e946bbc791f630c568b2b65899f644abd81))
+ - Ignore interrupts when reading inputs… ([`a3bf8be`](https://github.com/byron/prodash/commit/a3bf8beeb8f9496fa102ff59aeb4a7c6bdcac70e))
+ - Update to crossterm 0.18, but… ([`b5aa292`](https://github.com/byron/prodash/commit/b5aa292bd89cb223e9239bad3decc74495363b55))
+ - Update to tui 0.12 ([`a70e96d`](https://github.com/byron/prodash/commit/a70e96d64f52de0d3d4fcec9cbacbdf0fd6b7bb0))
+ - Upgrade to tui 0.12 ([`0606d46`](https://github.com/byron/prodash/commit/0606d4639c286c1917b85aee02f294dbadbaba77))
+</details>
+
+## v10.0.2 (2020-09-17)
+
+* Remove `futures-util` dependency
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 10 commits contributed to the release over the course of 3 calendar days.
+ - 3 days passed between releases.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - (cargo-release) version 10.0.2 ([`6e84aef`](https://github.com/byron/prodash/commit/6e84aef4df2d5d4c465abaa9b8a8a804f238e4ac))
+ - Changelog update ([`75258ea`](https://github.com/byron/prodash/commit/75258ea37c17975b2359ee0fdf34836430a7dd08))
+ - Remove dependency to futures-util ([`45b8dba`](https://github.com/byron/prodash/commit/45b8dbafd0d7e80d7169212ee4b1a0d696e8c4ec))
+ - Switch to release version of futures-lite ([`4fda4c0`](https://github.com/byron/prodash/commit/4fda4c05e5f4b7be2ff4a3b8898b060aa5c49ed4))
+ - Switch to latest version of futures-lite to remove stream::select_all ([`53ff638`](https://github.com/byron/prodash/commit/53ff638997736010f696db21c95c70cd87792ab0))
+ - Get rid of some more future-util functionality - just one more missing ([`0c82556`](https://github.com/byron/prodash/commit/0c82556cba25c16bd50c7abc39f8bb8078ea4228))
+ - (cargo-release) version 0.3.2 ([`e187a9d`](https://github.com/byron/prodash/commit/e187a9d4addcde2587f02fdffa1d7a06a58dd3a6))
+ - Crosstermion without futures-util! ([`d41352d`](https://github.com/byron/prodash/commit/d41352dff8954cf64ed31c2644aba5c4d846256b))
+ - (cargo-release) version 0.3.1 ([`0a6b6bc`](https://github.com/byron/prodash/commit/0a6b6bcb2ce629254e715ecdff5544303e7c2d2c))
+ - Upgrade futures-lite dependency ([`da021ea`](https://github.com/byron/prodash/commit/da021ea8306eaadb2bd40b155d4c431a164b6c42))
+</details>
+
+## v10.0.1 (2020-09-13)
+
+* upgrade dependencies to latest versions
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 5 commits contributed to the release.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Thanks Clippy
+
+<csr-read-only-do-not-edit/>
+
+[Clippy](https://github.com/rust-lang/rust-clippy) helped 1 time to make code idiomatic.
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - (cargo-release) version 10.0.1 ([`a401d55`](https://github.com/byron/prodash/commit/a401d55dffd8191308dcee55c3404d6e0d777ccd))
+ - Thanks clippy ([`2ea963c`](https://github.com/byron/prodash/commit/2ea963cb7bf31f63fa7d64e7b6183f0cb73cbcc8))
+ - Upgrade blocking ([`01db9af`](https://github.com/byron/prodash/commit/01db9af60103abc1c2dbc486943c5fe9878abb1b))
+ - Upgrade async-io ([`4d71843`](https://github.com/byron/prodash/commit/4d71843120b68fa431628627b6b5458b3576b70a))
+ - Upgrade to latest futures-lite ([`9eb47a6`](https://github.com/byron/prodash/commit/9eb47a671ea8befb7f16d805d9c7c57f0c72d5c2))
+</details>
+
+## v10.0.0 (2020-09-13)
+
+### Breaking
+
+* Enforce `Send + 'static` bounds for `Progress` trait.
+ * This way it's clear that Progress is supposed to work in a threaded environment, which is the environment they are used in most often.
+ * On the call site, this avoids having to specify these trait bounds explicitly.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 1 commit contributed to the release.
+ - 27 days passed between releases.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Progress with 'Send + 'static' bounds; bump major version ([`50a90ec`](https://github.com/byron/prodash/commit/50a90ece86e9642cb8005a7b1472d29b4b14f197))
+</details>
+
+## v9.0.0 (2020-08-16)
+
+### Breaking
+
+* add `set_name(…)` and `name()` to `Progress` trait.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 1 commit contributed to the release.
+ - 4 days passed between releases.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Add 'name' and 'set_name' methods to trait ([`94c4390`](https://github.com/byron/prodash/commit/94c4390af8cf2ba5c9fa03d6177cff72cb762ad8))
+</details>
+
+## v8.0.1 (2020-08-11)
+
+Add missing trailing paranthesis in throughput display
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 1 commit contributed to the release.
+ - 1 day passed between releases.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Add missing parenthesis; bump patch level ([`b2af5b9`](https://github.com/byron/prodash/commit/b2af5b938d2690d2feed23e9d02ce683c280aceb))
+</details>
+
+## v8.0.0 (2020-08-10)
+
+<csr-id-66800fd4e6c9f517f19da4e26a75cb3f139353b0/>
+<csr-id-64cfe9e87e038fb36492307dfb75cbc8204180d8/>
+
+### New Features
+
+* Provide various Units for improved formatting of durations, steps, counts and bytes, and specify display of percentages.
+* Support for throughput display if enabled in _line_ and _tui_ renderer, and when opted in when creating units.
+* Turn `ProgressStep` type into usize to allow for a broader range of values. Specifically, this enables counting bytes read and written
+ of files or streams bigger than 2^32-1
+* A new example program: **units**
+* Add methods to help determine key hierarchy, useful for writing custom renderers: `Key::shares_parent_with(…)`,`Key::adjecency(…)`
+* `Key::adjecency(…)`
+
+### Breaking
+
+* In `Progress::init(max, unit)` , `unit` now is `Option<Unit>`, existing code can be transformed using `progress.init(None, Some("label".into()))`.
+* moved`tree::progress` into `progress` and renamed `Value` into `Task` and `Progress` into `Value`
+* moved`tree::messages` into `crates::messages`
+* moved`tree::key` into `crates::progress::key`
+* moved `tree::Throughput` into `crate::Throughput`
+* **removed** `deep_eq()` method in `Root` tree
+* tui engine option `redraw_only_on_state_change` was removed without substitute
+* **Move and rename**
+ * `tree::ProgressState` → `progress::State`
+ * `tree::Value` → `progress::Value`
+ * `tree::Progress` → `Progress`
+* Remove `Hash` implementation for all public types except for `tree::Key`
+* Move `tui` and `line` renderers into the `render` module
+* Rename `log-renderer` feature to `progress-tree-log`
+* Rename `tui-renderer*` into `render-tui*` and `line-renderer*` into `render-line*`
+
+### Other
+
+ - <csr-id-66800fd4e6c9f517f19da4e26a75cb3f139353b0/> Attempt to impl throughput in display…
+ …which can't work because it's actually never mutable due to the way
+ drawing work: it operates on a snapshot, a copy, that is not written
+ back.
+
+ And even if it was, the type system statically concludes sync is needed
+ as well for this to work.
+
+ Long story short: No state changes are ever allowed with a system like
+ this, and throughput needs to maintain just that.
+
+ Throughput must be implemented in each renderer.
+ - <csr-id-64cfe9e87e038fb36492307dfb75cbc8204180d8/> Try to manually implement/run a local executor
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 91 commits contributed to the release over the course of 19 calendar days.
+ - 19 days passed between releases.
+ - 2 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Thanks Clippy
+
+<csr-read-only-do-not-edit/>
+
+[Clippy](https://github.com/rust-lang/rust-clippy) helped 2 times to make code idiomatic.
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - More precise throughput computation ([`360d3ee`](https://github.com/byron/prodash/commit/360d3ee664b21c126b087ada5cd897927e0389bc))
+ - Remove throughput field which was effectively constant ([`52661ee`](https://github.com/byron/prodash/commit/52661eea38a511d3b1b47d63fd9ef9ac0f46caaf))
+ - ThroughputOnDrop utility ([`a4ea34c`](https://github.com/byron/prodash/commit/a4ea34c5a0f5370e7d109abccab3872c4f38aa8d))
+ - Provide a way to take out progress from a DropOrDiscard (for completeness) ([`2d57a54`](https://github.com/byron/prodash/commit/2d57a54c1508bc9314713384fe9cb08f949cbf76))
+ - Make throughput printing more flexible, allowing to override value and unit ([`a80104c`](https://github.com/byron/prodash/commit/a80104c955675c6cc40adcd8b90885010bfc9d0a))
+ - Format throughput utility with less arguments and using available Unit ([`0e84270`](https://github.com/byron/prodash/commit/0e8427029abb2d87244aa51669ea82086b545b81))
+ - Fix benchmarks ([`7c5be6b`](https://github.com/byron/prodash/commit/7c5be6b62a85d8b06d1f2609203e38173903e611))
+ - Make 'Log' more intuitive to reach ([`8cf7452`](https://github.com/byron/prodash/commit/8cf7452b795d0d1b7e16df75cf4a1cd4c3cbec3f))
+ - Convenience functions for nicer use of render() ([`4b396f5`](https://github.com/byron/prodash/commit/4b396f5f49d879006801cd678a17628ec56f3d19))
+ - Conform feature names for renderers ([`673d149`](https://github.com/byron/prodash/commit/673d149387b745944915ae1472e7a52bbd0d48de))
+ - Conform feature name ([`8ba1e96`](https://github.com/byron/prodash/commit/8ba1e96702bccb36886fe99bb0fa5eb7c58af7b9))
+ - Thanks clipppy ([`72bfa47`](https://github.com/byron/prodash/commit/72bfa47d468b2ecaf6086b62b2d1a25c860f1baa))
+ - Log progress now works with units ([`37f1266`](https://github.com/byron/prodash/commit/37f1266131bf52b35e5c473305f4c1d07df468f2))
+ - First bare addition of log progress (from gitoxide) ([`006ba9d`](https://github.com/byron/prodash/commit/006ba9db0345953ff2b76ec4f28f9b58a60c7ec1))
+ - Feature toggle for dashmap backed tree ([`fa688c8`](https://github.com/byron/prodash/commit/fa688c80e7ba264c5b87ae06b9aadebacb0d823e))
+ - Make tui renderer use Root trait ([`3da74b0`](https://github.com/byron/prodash/commit/3da74b0aff7c615911f0259336b7ec28a11249d1))
+ - Use Root trait in line renderer ([`a977c44`](https://github.com/byron/prodash/commit/a977c446534ea7c18f037e2a51dcbc419aac7c3d))
+ - Implement Root trait for tree::Root ([`ec5c673`](https://github.com/byron/prodash/commit/ec5c6732098f9f90ba62184aacdd76f397d3d8a8))
+ - Allow general use of Throughput by moving it into the top-level ([`cdabdce`](https://github.com/byron/prodash/commit/cdabdceb7ba9db76630f0cf58a7a4e2d4cc03f6f))
+ - Move `tree::key` into `progress::key` ([`d6f66b7`](https://github.com/byron/prodash/commit/d6f66b785a12f3972c29391df8a8d842cfed2a46))
+ - Refactor ([`63fd65a`](https://github.com/byron/prodash/commit/63fd65a82c57af25023ee9615662481b49e1d4e3))
+ - First stab at sketching out the Root trait - it needs a key, however :D ([`08d30c6`](https://github.com/byron/prodash/commit/08d30c61f12aa69a66e46639016e4c9c15300a20))
+ - Upgrade `init` to support the `Unit` type ([`226a849`](https://github.com/byron/prodash/commit/226a84944c8caba5aa7abac99e3d815b33d5f935))
+ - Add `Progress` trait and implement it for `tree::Item` ([`93ffb60`](https://github.com/byron/prodash/commit/93ffb6037bec833fdf2d0bc51bbe6632d06aa17a))
+ - Rename `Value` -> `Task` and `Progress` -> `Value` ([`f021ed8`](https://github.com/byron/prodash/commit/f021ed8f26faa360bfbd68b3a4479540dcea2942))
+ - Move `tree::messages` into `crate::messages` ([`4bce1d7`](https://github.com/byron/prodash/commit/4bce1d742874dd2708f04f544d2979d8084c4667))
+ - Moved tree::progress::* back into `crate::progress` ([`cf7405c`](https://github.com/byron/prodash/commit/cf7405c3d0ef6d6cbade68f68a1615a04e34bde8))
+ - Prepare for generalizing the root interface to keep renderers general… ([`4e38c6b`](https://github.com/byron/prodash/commit/4e38c6bb1ea157fb067dd61f015e2cb0a2855c70))
+ - Move most progress related types back to tree, however… ([`e6f242e`](https://github.com/byron/prodash/commit/e6f242e8555b2ef0fc15d6cf25273a991332f47c))
+ - Move line and tui renderers into `render` module ([`31358a7`](https://github.com/byron/prodash/commit/31358a777b577772b28b9f58ba797d41fe5f4b00))
+ - Thanks clippy ([`7435f35`](https://github.com/byron/prodash/commit/7435f35f9ddd88443eb87c0fe13256172d23d0f5))
+ - Disable throughput for dashboard as it's not reqired ([`be39f49`](https://github.com/byron/prodash/commit/be39f49fa2e84139c6af2e870dcf079b234cabe6))
+ - Remove Hash from types that don't need it ([`95e89ae`](https://github.com/byron/prodash/commit/95e89ae3ac18ca0e302b84695a1b6043ecbc0ee2))
+ - Now it works! ([`2e91ed2`](https://github.com/byron/prodash/commit/2e91ed217f241290ffcb36c77e178f26393df422))
+ - First attempt to bring throughput to TUI ([`d0e3af3`](https://github.com/byron/prodash/commit/d0e3af34ebf5f01bc1923e0024667200361a104b))
+ - Refactor ([`9212de4`](https://github.com/byron/prodash/commit/9212de4662f7e35492a8796d5ef4dcece91e5ea8))
+ - Throttling throughput recomputation to 1s really does the trick ([`6a4ae4e`](https://github.com/byron/prodash/commit/6a4ae4e7b933695e4c32359237c97cacc543ea8c))
+ - First working example for throughput, but… ([`0ceebd4`](https://github.com/byron/prodash/commit/0ceebd4fa0e3b25e15cbeb3b68dddc76b28515e2))
+ - First dummy throughput impl does what it should, nice! ([`e2ffb04`](https://github.com/byron/prodash/commit/e2ffb047b2eab3785b33db0925e7503d8e6300ea))
+ - Possibly working impl of getting the throughput + reconcile ([`74315bf`](https://github.com/byron/prodash/commit/74315bf28e8451ef8008c6b98630b8984db38464))
+ - Integrate calls to (optional) throughput into line renderer ([`a1af8fc`](https://github.com/byron/prodash/commit/a1af8fc78b7e5f8fd49b28df6352e9690caed8af))
+ - First sketch of Thoughput handler ([`2edeefc`](https://github.com/byron/prodash/commit/2edeefca27592583186bca914b50e4bf6fc5f7e7))
+ - Refactor ([`80684b3`](https://github.com/byron/prodash/commit/80684b3b60b069a6942b790c9b308649f432dbc4))
+ - Refactor ([`19e0901`](https://github.com/byron/prodash/commit/19e090164be073351b16ded0a75168b48a5cf654))
+ - Refactor ([`0402040`](https://github.com/byron/prodash/commit/040204078802f1d97a1a186f75d2e61cda3dd5d5))
+ - Refactor ([`b981d2e`](https://github.com/byron/prodash/commit/b981d2eb055248ee67ada33da3b4ef946a842e8d))
+ - Make key adjecency helpers public ([`3b12ea2`](https://github.com/byron/prodash/commit/3b12ea292468ed745d54267b7635b6b67ea84195))
+ - Thanks clippy ([`a99e791`](https://github.com/byron/prodash/commit/a99e791427b5e2af4ad421545a650ca03c8f4e6c))
+ - Refactor ([`c6068c5`](https://github.com/byron/prodash/commit/c6068c58bbcbf8faa28d9dc7d5ec3766ec41d953))
+ - Refactor ([`983d2e5`](https://github.com/byron/prodash/commit/983d2e5c90773694dcd42fa54b5574a61621cf44))
+ - Refactor ([`0c537ab`](https://github.com/byron/prodash/commit/0c537ab65af9d388e02fdea7175e7fbcd4fb4a3e))
+ - Basic display of timespans for throughputs ([`fd68710`](https://github.com/byron/prodash/commit/fd687101407eced316b544dff48bb914acc5cf7f))
+ - Refactor ([`40869a8`](https://github.com/byron/prodash/commit/40869a8322faa352c0d082f7ee33c2e533d9836d))
+ - Prepare to move throughput calculations into renderer ([`682dee2`](https://github.com/byron/prodash/commit/682dee2ad35766c56e01458d0ec151587079ee4a))
+ - Attempt to impl throughput in display… ([`66800fd`](https://github.com/byron/prodash/commit/66800fd4e6c9f517f19da4e26a75cb3f139353b0))
+ - Pass elapsed time on in tui renderer ([`eb22417`](https://github.com/byron/prodash/commit/eb224176f041de36f9a6ab42888bceef0d3b85c5))
+ - Pass elapsed time on in line renderer ([`33be555`](https://github.com/byron/prodash/commit/33be555f1e6c5141c5ee756a1d42b7ea0762f975))
+ - Allow providing an optional elapsed duration… ([`a4b4ab7`](https://github.com/byron/prodash/commit/a4b4ab7d95457f7b662fb98d12eefb2bd0c34f39))
+ - First test for throughput - before major refactoring ([`963c933`](https://github.com/byron/prodash/commit/963c933ebd2f5dcb090460dc79c445e122731c9a))
+ - Remove unnecessary tui option: redraw_only_on_state_change ([`f78cf4f`](https://github.com/byron/prodash/commit/f78cf4fe8740b55f5c363151eab2ee1da558390c))
+ - Prepare throughput display ([`3266fad`](https://github.com/byron/prodash/commit/3266fad2ad5e63855eb92c1f0c390bf2bfd8167f))
+ - Make Unit a struct, introduce new 'Kind' to capture dynamic and static strings of display value ([`b413111`](https://github.com/byron/prodash/commit/b41311114ce3be7174dea7c1751fb15b20284794))
+ - Prepare Mode to carry information about throughput handling ([`ea705ac`](https://github.com/byron/prodash/commit/ea705ac4089d7958e97b81f3ee1f8261e9948d2f))
+ - Fix benches ([`a2d35fb`](https://github.com/byron/prodash/commit/a2d35fb096a915c0a55985f619a12149add29251))
+ - Finish simple units example, showing all available units ([`de6addd`](https://github.com/byron/prodash/commit/de6addde16da64a7884eca8de221d398d868171c))
+ - First simple progress bar using bytes ([`e3bdbf1`](https://github.com/byron/prodash/commit/e3bdbf1a8ef5c132405ec6570422b87ed07360ce))
+ - Frame for new example program ([`2f1cb12`](https://github.com/byron/prodash/commit/2f1cb125b455c7357fd6576f3180e145c5efdc44))
+ - Finally run unit-tests as well ([`70d7ae2`](https://github.com/byron/prodash/commit/70d7ae2bcdf98e60f83286cf12e53e0690893eef))
+ - Refactor ([`6a18584`](https://github.com/byron/prodash/commit/6a185843344c1f54efc70f8bcf0e3736b6be87b0))
+ - Use new Unit type everywhere ([`539edde`](https://github.com/byron/prodash/commit/539eddecb9d91d315771c8f30339c3b5cc186e18))
+ - Support for nicer duration display ([`d500412`](https://github.com/byron/prodash/commit/d500412766c850133569c200cc13f6040a665af2))
+ - Integrate humantime ([`f0f55bb`](https://github.com/byron/prodash/commit/f0f55bb6660523fe02cddc764a79fe2e3b459ce7))
+ - Make range more compliant to aid consistent coloring ([`afb0c91`](https://github.com/byron/prodash/commit/afb0c91a20bd187f271791056238b0563015fbd1))
+ - A new range display mode, only good with an upper bound ([`b83d6bd`](https://github.com/byron/prodash/commit/b83d6bd7137ea0dc8e9731552ae125cec6cb8631))
+ - Support for omitting the unit ([`f350079`](https://github.com/byron/prodash/commit/f350079a9db9433936f0f90fe9316d0bed65cf38))
+ - Using fmt::Write is so much better! ([`bf89a3d`](https://github.com/byron/prodash/commit/bf89a3d11fcc6eba087da1d28cace7991156bb39))
+ - Trying to use byte-size shows that the trait interface isn't flexible enough ([`f233d43`](https://github.com/byron/prodash/commit/f233d43f2d48bb911cb20808396d4a2b0d55058d))
+ - Allow splitting up generation of values and unit for more creative control… ([`0e15b97`](https://github.com/byron/prodash/commit/0e15b97d6a7cf5dcd7be068de8bf3f4b6472089c))
+ - Percentage support ([`43c0980`](https://github.com/byron/prodash/commit/43c0980ffe15efe5e4994398d16b519d9e19836e))
+ - First small steps towards implementing unit and value display correctly ([`5bd90cc`](https://github.com/byron/prodash/commit/5bd90cc6539bf61019de6d096589b1c2caeb685e))
+ - Refactor ([`716e774`](https://github.com/byron/prodash/commit/716e77489aedd9df54745793c979af67acfbb41f))
+ - Refactor in preparation for another example application ([`6063c27`](https://github.com/byron/prodash/commit/6063c2796b0b2508a73eb41b0c6c9ed8a601e21b))
+ - Rough layout on how to get much more powerful unit and value rendering ([`90a9c2d`](https://github.com/byron/prodash/commit/90a9c2dac084b5ee9a60065875401c57644dce00))
+ - First sketch for new Unit type and trait to display what we are interested in ([`129d09d`](https://github.com/byron/prodash/commit/129d09d6190619e64144a93d617b65f434d26f50))
+ - Use 'usize' as ProgressStep, instead of u32 ([`79ae31f`](https://github.com/byron/prodash/commit/79ae31fc1dde5120a6e2706bc84995f72cc587dd))
+ - Line renderer: more compact progress bar ([`8071110`](https://github.com/byron/prodash/commit/8071110e8a41612808206c3f8f444542818f4a51))
+ - Revert "FAIL: Try to manually implement/run a local executor" ([`f6fa7ab`](https://github.com/byron/prodash/commit/f6fa7ab681549abfd77a14e4c8015f623f720c83))
+ - Revert "Another attempt of using a local executor: FAIL" ([`a3254a6`](https://github.com/byron/prodash/commit/a3254a6aac6b4150bcbd10004d55d4cc06d45dbe))
+ - Another attempt of using a local executor: FAIL ([`ee6275c`](https://github.com/byron/prodash/commit/ee6275ca3f0da906b9f94e6d95d9ce130907e9c8))
+ - Try to manually implement/run a local executor ([`64cfe9e`](https://github.com/byron/prodash/commit/64cfe9e87e038fb36492307dfb75cbc8204180d8))
+ - Actually only a few lines are needed to drive multi-task ([`d19a1db`](https://github.com/byron/prodash/commit/d19a1db08305abb77505868cc237b1c71491960d))
+</details>
+
+## v7.1.1 (2020-07-22)
+
+* dependency update: smol 0.2
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 2 commits contributed to the release.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Patch bump ([`33f57ef`](https://github.com/byron/prodash/commit/33f57efcd25523318d4c494dfd29d538be964992))
+ - Upgrade to smol 2.0 ([`ab7dcb4`](https://github.com/byron/prodash/commit/ab7dcb4f2b108fbf7f525b61110f3f6b6339d0b0))
+</details>
+
+## v7.1.0 (2020-07-22)
+
+* Improved looks thanks to bold fonts
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 5 commits contributed to the release.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Bump minor version ([`aac8cb2`](https://github.com/byron/prodash/commit/aac8cb2ce99c3eb70b42919754e6ecefcab43854))
+ - Use bold characters for some highlights ([`56cf67e`](https://github.com/byron/prodash/commit/56cf67e36ed0db6a4a78e1d30e7fc4eebe7c7342))
+ - Upgrade to tui 0.10 ([`79afe12`](https://github.com/byron/prodash/commit/79afe123dda6e892425772fca8e040ac256edf9d))
+ - Crosstermion 0.3 with support for tui 0.10 ([`dcda91b`](https://github.com/byron/prodash/commit/dcda91b744cb9f4f735591a53997ac4f2747a61e))
+ - Decouple prodash from local crosstermion for tui migration ([`1105cfd`](https://github.com/byron/prodash/commit/1105cfd46d9a163c6e6d6c761fec8f70a0b08156))
+</details>
+
+## v7.0.4 (2020-07-21)
+
+* **tree::Item**
+ * Add new methods `inc_by(step)` and `inc()` for convenience
+* **line renderer**
+ * They now look clearer, as they changed from \[===> ] to \[===>------]
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 7 commits contributed to the release over the course of 1 calendar day.
+ - 9 days passed between releases.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - (cargo-release) version 7.0.4 ([`45a8a00`](https://github.com/byron/prodash/commit/45a8a00a76c3beed0baf0cffaa5656f0835a927e))
+ - Improve line progress rendering ([`f694bb9`](https://github.com/byron/prodash/commit/f694bb995d6b3f3ef9c25b584bbd59bf7b625c7c))
+ - Add convenience methods to tree::Item ([`3a36ce6`](https://github.com/byron/prodash/commit/3a36ce6082b3c6d24c8196ed4e9c537a5f36cb4f))
+ - Prioritize the tui engine for futures-lite ([`c17235c`](https://github.com/byron/prodash/commit/c17235c493fffb628b15a730aa72cdd39f93ca12))
+ - Attempt to switch to futures-lite, but a few things are missing ([`c8e52c2`](https://github.com/byron/prodash/commit/c8e52c2c045ddcd33f6f40d3f56ffbb3d00eb012))
+ - Remove unused dependency ([`85c1f6d`](https://github.com/byron/prodash/commit/85c1f6d894f651a006793dfa0df3a547c8ed4a29))
+ - Remove futures-util nearly completely - missing filter_map() extension for stream ([`6fc5846`](https://github.com/byron/prodash/commit/6fc58461028e63ca333adf7381178473329fe643))
+</details>
+
+## v7.0.3 (2020-07-11)
+
+cleanup and code simplification in the line renderer.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 3 commits contributed to the release.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Bump patch level ([`ffc58bb`](https://github.com/byron/prodash/commit/ffc58bbd0d305b4e3c1b33f5a6661cea4b692def))
+ - Crosstermion 0.2 with much less code and complexity ([`f3b24d0`](https://github.com/byron/prodash/commit/f3b24d0edfdc40c5fb045facd467aab2e7e53f12))
+ - Greatly simplify line renderer engine code ([`f0eaf27`](https://github.com/byron/prodash/commit/f0eaf279c42f0a7d28bc6b4c6d064df072f3a3a7))
+</details>
+
+## v7.0.2 (2020-07-11)
+
+* **render-line** `JoinHandle` will
+ * now send a signal to perform a render before shutting down to capture the final state
+ * wait for the render thread to complete the aforementioned actions on drop. You can still override this behaviour through
+ `disconnect()` or `forget()`.
+ * removed special code-paths that avoided bringing up another thread for 'ticks' at the expense of shutdown delay.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 2 commits contributed to the release.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Bump patch level ([`9a7c2ef`](https://github.com/byron/prodash/commit/9a7c2efec958c5b0729e799177a94f5089882158))
+ - Various improvements to help integrating with the line renderer ([`3711394`](https://github.com/byron/prodash/commit/371139409619f4a3195aaeabc8bf38a3b3ec6209))
+</details>
+
+## v7.0.1 (2020-07-10)
+
+Prevent cursor movement if no progress bar is drawn.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 3 commits contributed to the release.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Bump patch level ([`b1ca7fa`](https://github.com/byron/prodash/commit/b1ca7fa48d35a325566c90f49382b9385580be02))
+ - Don't cause cursor movement when using '0' as MoveUp value ([`d090c0c`](https://github.com/byron/prodash/commit/d090c0ccbce3c4323eb03dc31fc550b7c11f2cf2))
+ - Add new asciicast ([`443702b`](https://github.com/byron/prodash/commit/443702bf55c7e2677f867e1c5557dac0de78998b))
+</details>
+
+## v7.0.0 (2020-07-10)
+
+<csr-id-a684188b3eee0cc67fe48b9ae14aa9cd63603caf/>
+<csr-id-1bc5c764c9b1190f168d076b2183a27569750421/>
+
+Add new render-line, change feature flag names.
+
+### Other
+
+ - <csr-id-a684188b3eee0cc67fe48b9ae14aa9cd63603caf/> first version of 'slow' event loop which actually won't respond quickly either :D
+ - <csr-id-1bc5c764c9b1190f168d076b2183a27569750421/> bump patch level
+
+### New Features
+
+* **line**
+ There is a new line renderer as neat trade off between bare logs and full-blown tui. It will work best with 'simple' and not
+ too dynamically changing progress trees.
+ Activate it with the `render-line` + one of `render-line-crossterm` or `render-line-termion` feature flags.
+* Activate it with the `render-line` + one of `render-line-crossterm` or `render-line-termion` feature flags.
+
+### Breaking Changes
+
+* **`tui` module**
+ * **TuiOptions** -> **Options**
+ * `render_with_input` now takes the Write stream as argument, instead of defaulting to `std::io::stdout()`
+* **Feature Flags**
+ * **with-crossterm** -> **render-tui-crossterm**
+ * **with-termion** -> **render-tui-termion**
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 81 commits contributed to the release over the course of 4 calendar days.
+ - 4 days passed between releases.
+ - 2 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Minior cleanup ([`48879f3`](https://github.com/byron/prodash/commit/48879f3cf44d1b8531819f7c4d3ca84d0bf116e9))
+ - Dynamically resize and align progress bars - looks so much better now! ([`021dc5a`](https://github.com/byron/prodash/commit/021dc5abda66dad0c2270e5db093b3772e56e7b3))
+ - Dynamically recompute dimensions of log messages ([`5cb7fea`](https://github.com/byron/prodash/commit/5cb7fea8e60b829ea32fec1c1a161a3cddb7cb5e))
+ - Pass entire terminal dimensions to line buffer… ([`03a859c`](https://github.com/byron/prodash/commit/03a859c09f217c0d8e39b0ba7d2f05429fd92b16))
+ - Allow setting the terminal width using CLI in dashboard example ([`bfd1a98`](https://github.com/byron/prodash/commit/bfd1a98990f9c7628bd82d6c1391dca8f783f570))
+ - Always show log messages, but show progress only after initial delay. ([`7ec5530`](https://github.com/byron/prodash/commit/7ec5530ceed8c6b32b0a82fbe0f2d5e986d57a20))
+ - Initial progress messages should actually be shown despite initial delay ([`4d68060`](https://github.com/byron/prodash/commit/4d6806028a780633e24966ca9900e0a517e6b783))
+ - More fine-grained control over the amount of work displayed ([`9f4fa49`](https://github.com/byron/prodash/commit/9f4fa4938fac88ac2b70ebb96b52ddf7152cbd04))
+ - Progress drawing now can be without color as well ([`f980a83`](https://github.com/byron/prodash/commit/f980a83226489dc1a1066a488aa39eb3c4d54cba))
+ - Release notes ([`f1280a9`](https://github.com/byron/prodash/commit/f1280a9e18d3fc530cf7eb42eeb834d986c91477))
+ - Nobody needs ticks right now, and using the step for unbounded progress rocks… ([`298afe6`](https://github.com/byron/prodash/commit/298afe685589827ab9a882f37ee53726486bd678))
+ - Ascii-only unbounded progress ([`ab93db2`](https://github.com/byron/prodash/commit/ab93db261ec994ea8874c09015a1031ed91b70a1))
+ - Refactor ([`a4aa7e8`](https://github.com/byron/prodash/commit/a4aa7e8fb894be910d0e297eb1be6c495641fd9f))
+ - Reverse direction of unbounded progress by reversing an iterator, nice! ([`380330d`](https://github.com/byron/prodash/commit/380330d1e97011bc0ca1668d4686691b4a3986be))
+ - A first, but backward, version of unbounded progress ([`89fb23c`](https://github.com/byron/prodash/commit/89fb23ca701eefa8fac9bb54697050be0ea30d28))
+ - A somewhat usable display of bounded progress ([`8ebde51`](https://github.com/byron/prodash/commit/8ebde51451792a0b45080e364db7b840bece0282))
+ - Prepare nicer but non-unicode drawing to realize we really want to configure the width ([`54e1ea7`](https://github.com/byron/prodash/commit/54e1ea77395d1da7a4bb5e91c47cc5949282780c))
+ - Overdraw for log lines - works perfectly! ([`91ea381`](https://github.com/byron/prodash/commit/91ea381f8edbe9bdf2c19a2f0fdb5d4064bc19a3))
+ - Make 'hiding the cursor' configurable ([`03a8a9e`](https://github.com/byron/prodash/commit/03a8a9ee9236a53deafdd341cd2dce18e32c05a8))
+ - Fix 'make check' ([`a7bff83`](https://github.com/byron/prodash/commit/a7bff8372416f10d0ecb4175d20cb5b08529fe13))
+ - Refactor ([`3a1b6c5`](https://github.com/byron/prodash/commit/3a1b6c5726c12a0051e255563aeb822af298f98d))
+ - Refactor ([`fb91dde`](https://github.com/byron/prodash/commit/fb91dde5cf89d3efc44d554ee1eb433d72b5d542))
+ - Show and hide the cursor if the ctrl+c is pressed (or SIG_TERM is sent) ([`ff98952`](https://github.com/byron/prodash/commit/ff98952c1f0310d32e580d5ba56d4615b64a24ed))
+ - Refactor ([`dcb24ea`](https://github.com/byron/prodash/commit/dcb24eaac1f31bb07bc3fea5a8893018c7b220b6))
+ - Use a vecdeque as it is just perfect for what we need to do here ([`8b7dbaa`](https://github.com/byron/prodash/commit/8b7dbaa9cbd2b712ed98f9af5c0ed897f95f0ebd))
+ - Basis for overdraw of log messages ([`7a4aecf`](https://github.com/byron/prodash/commit/7a4aecfef256ee4d4a21059a2ef35b010d3514e3))
+ - Obtain terminal size from crossterm or termion, now the dashboard… ([`3484fed`](https://github.com/byron/prodash/commit/3484fedd0a7b20b065edda3c101b31b2f22fd880))
+ - Support for termion version of the dashboard ([`a37cb2a`](https://github.com/byron/prodash/commit/a37cb2a03f832ee05c092c5bec0b481c1d922440))
+ - Incorporate filtering into the line count logic - that works actually ([`e38e559`](https://github.com/byron/prodash/commit/e38e55954d3bfcb016e23dac4f24b866e59aa302))
+ - Fix select issue, surprise. Crossbeam channels are certainly more robust ([`5be4885`](https://github.com/byron/prodash/commit/5be4885816d2ec2fccf2a0d2a01deb38e740edc4))
+ - Make interval ticker actually work - looks like flume can't always select ([`af710d4`](https://github.com/byron/prodash/commit/af710d4391d8103ee03dd385285159bd1d61eff3))
+ - Quite a failed attempt to move cursor back up for overdrawing… ([`04c686b`](https://github.com/byron/prodash/commit/04c686b6bec543e290b729b61ac69245953a4564))
+ - First rough drawing logic without cursor movement ([`0134e0d`](https://github.com/byron/prodash/commit/0134e0d54357d4a541555d5749e91b55ede7a692))
+ - Manually adjust fill length for correct results even with Chinese ([`4da38f2`](https://github.com/byron/prodash/commit/4da38f270c713ed2f6254154e06e655eee4dbae5))
+ - Try to align messages a bit more nicely, but… ([`2744e64`](https://github.com/byron/prodash/commit/2744e643d91ec411814df84dd755a9ffd304a9c1))
+ - Turn off timestamps by default ([`dd02770`](https://github.com/byron/prodash/commit/dd02770db79fc104b62a727cdf79266ca6f11298))
+ - Draw time as well in line renderer ([`fa51fad`](https://github.com/byron/prodash/commit/fa51fad3e0467f25d949c1dfb02cb38d10bb322f))
+ - Revert "Add time support to crosstermion - seems a bit out of place actually" ([`93397c7`](https://github.com/byron/prodash/commit/93397c7f89e9c3d790cf2552690de1ef66f9e2d1))
+ - Make 'time' module available to be shared by multiple renderers ([`c31d1c5`](https://github.com/byron/prodash/commit/c31d1c5adf6f83d0ffd44d91d8032f835038bb60))
+ - Add time support to crosstermion - seems a bit out of place actually ([`8e2bf7c`](https://github.com/byron/prodash/commit/8e2bf7cceb4f96d9fad523944868102951be094d))
+ - Move conditional painting code into crosstermion - could be useful for dua as well ([`6d04514`](https://github.com/byron/prodash/commit/6d04514d9015d6dcfec7688d6deab06b6ac33f54))
+ - First sketch of conditional drawing of log messages ([`78f56c0`](https://github.com/byron/prodash/commit/78f56c0ffa4bea77a6ab7458734ad2cd206ac3e8))
+ - Add a new 'color' module for crosstermion ([`272a852`](https://github.com/byron/prodash/commit/272a8525f5a102680f620d31d953467beae0b485))
+ - Fix division by zero when copying the entire message buffer ([`60f1bb8`](https://github.com/byron/prodash/commit/60f1bb8cb47b6f0720b064db811416eea52313bb))
+ - Cargo clippy ([`20f3144`](https://github.com/byron/prodash/commit/20f31449ae8e0c9de8c72f9286a7174a05972585))
+ - Add support for no-line-color flag; implement additional line renderer options ([`eda7fb3`](https://github.com/byron/prodash/commit/eda7fb32c8c621545824ecb7c37e7bfdd96cb3d3))
+ - Make coloring configurable, following some informal specs ([`1e1a02a`](https://github.com/byron/prodash/commit/1e1a02ab2c82ab332d5cbc8d5966810574cced8d))
+ - Integrate message copying into line renderer ([`79efb09`](https://github.com/byron/prodash/commit/79efb094c92bb99eae77391dd3ffc3551c79102b))
+ - Copying only new messages seems to work now ([`982dfee`](https://github.com/byron/prodash/commit/982dfeedb10d32d469fc3424b74d9629a82c3cd5))
+ - Refactor ([`3bb08f7`](https://github.com/byron/prodash/commit/3bb08f707503c802a234c18e03af7ebcd578dff4))
+ - Initial mostly working version of copying only new messages from the buffer. ([`d78472c`](https://github.com/byron/prodash/commit/d78472c579bfa20f4b2cc3fd2a062dac330dfdbf))
+ - Allow for a little more space when formatting :) ([`4bf4431`](https://github.com/byron/prodash/commit/4bf44310c9e1c37644e07092e1c5c80c9e6c45d4))
+ - First beginnings of testing copy_new(…) ([`917dd05`](https://github.com/byron/prodash/commit/917dd05122c39cd065eb5b7b761d083bc003a86f))
+ - Now actually fix the 'copy_all(…)' method for the message buffer :D ([`2bb088d`](https://github.com/byron/prodash/commit/2bb088d48408dc2106dbe5ad2bed8db5699bba15))
+ - Sketch for stateful message copying to copy only new ones. ([`90750c7`](https://github.com/byron/prodash/commit/90750c759a76d08b40b5e1b26d0d8d975afc1e2a))
+ - Fix off-by-one error in messsage buffer copy handling ([`c6e1ece`](https://github.com/byron/prodash/commit/c6e1ecefff385e4acc56047654a527316f7fd5ac))
+ - Refactor ([`ccc4297`](https://github.com/byron/prodash/commit/ccc4297a0e5dadca81a8398ae6495abbbf83cdd9))
+ - Refactor ([`ed338e0`](https://github.com/byron/prodash/commit/ed338e05c97085e0e7673d148f6d7f382a88ef03))
+ - Flesh out join handle with complete and symmetric API ([`1bd4476`](https://github.com/byron/prodash/commit/1bd4476b532425f0b7b2fdc82f058ed8b6b8317a))
+ - Support for interruptable initial delay ([`c15af5e`](https://github.com/byron/prodash/commit/c15af5ef2fe7c1bfde74f6d183c22688f666f398))
+ - Dashboard can now bring up the line renderer ([`f20f002`](https://github.com/byron/prodash/commit/f20f00282836f33465a372bf42ced1b04a3ca064))
+ - Don't assume quitting is requested on channel disconnect; allow detaching the handle ([`d050243`](https://github.com/byron/prodash/commit/d050243e8176a35ec29905a047e1a69b44ddf0e0))
+ - Frame to allow using 'line' renderer in dashboard example ([`4566e46`](https://github.com/byron/prodash/commit/4566e46155656e573af80fabd9df0ab6aec95531))
+ - Make dashboard depend on line renderer ([`ea861a2`](https://github.com/byron/prodash/commit/ea861a26e3ca8f6db37a0da79465531c2299c926))
+ - Now we are talking: Selector functions shouldn't have side-effects, 'wait()' is exactly it ([`9ebbc16`](https://github.com/byron/prodash/commit/9ebbc1685e4f7802cb6384711a06199d9cd8a58a))
+ - First version of 'slow' event loop which actually won't respond quickly either :D ([`a684188`](https://github.com/byron/prodash/commit/a684188b3eee0cc67fe48b9ae14aa9cd63603caf))
+ - Sketch draw logic for draw loops that are fast enough ([`b4db64e`](https://github.com/byron/prodash/commit/b4db64e3a134cea153db92ccb19dbc20d9ab9ee0))
+ - Sketch initial interface for line renderer ([`1712221`](https://github.com/byron/prodash/commit/1712221f04efb20717dbe662a8d29d4a23235989))
+ - Move changelog information into its own file ([`5aa3034`](https://github.com/byron/prodash/commit/5aa3034a2085fdf602641c7899448e30941183be))
+ - Prepare arrival of the mighty line-renderer feature :D ([`511389e`](https://github.com/byron/prodash/commit/511389e7491d11e06815b86f41b594a9df7c0529))
+ - Make external crates available as re-exports ([`0b8a763`](https://github.com/byron/prodash/commit/0b8a763585d7828f6ac1ccbcc23d985ff1eab3a9))
+ - Notes about the tradeoffs in backend choice ([`a942e85`](https://github.com/byron/prodash/commit/a942e85b62118c447214f683f3c866e502842337))
+ - Write down insights about coloring in terminals, in conjunction with crosstermion ([`c96abdb`](https://github.com/byron/prodash/commit/c96abdbc116801eb873e021151a853599f32ec43))
+ - Bump patch level ([`c879dfa`](https://github.com/byron/prodash/commit/c879dfa59fa4de6f7b0a02de42668224d791e5db))
+ - Fix description of crosstermion ([`8781c9f`](https://github.com/byron/prodash/commit/8781c9f3f70e12d436c957834f0a6dd61a74651b))
+ - Fix precedence in terminal module, crossterm is winning over termion now ([`521dd23`](https://github.com/byron/prodash/commit/521dd23dc67d6dee52cfbe0c35c3fe8fc28dc881))
+ - Bump patch level ([`1bc5c76`](https://github.com/byron/prodash/commit/1bc5c764c9b1190f168d076b2183a27569750421))
+ - Make sure conversions are always compiled ([`5a03628`](https://github.com/byron/prodash/commit/5a03628b1cafd8d06476f364242d0e149f4fad18))
+ - Fix flume features, bump to 0.1.2 ([`11d6665`](https://github.com/byron/prodash/commit/11d6665c77a1e4ce4dc262744ffb76dc8b907832))
+ - Bump patch level; add 'input-thread-flume' support ([`7fdbb72`](https://github.com/byron/prodash/commit/7fdbb72822c450a4e13eba998236b608cf9eeca3))
+ - Fix Cargo.toml to allow 'cargo test' to work without specifying features ([`748ab4b`](https://github.com/byron/prodash/commit/748ab4be6aa5fc975fcdcacbd92a2fa388103734))
+</details>
+
+## v6.0.0 (2020-07-05)
+
+<csr-id-bbf2651e379b5758d53a889d9fb220c616d2a096/>
+
+Factor terminal input into the new `crosstermion` crate.
+
+Due to this work, the default features changed, which is a breaking change for those who relied on it.
+Now when using the `render-tui`, one will also have to specify either the `with-crossbeam` or `render-tui-termion` feature.
+
+### Other
+
+ - <csr-id-bbf2651e379b5758d53a889d9fb220c616d2a096/> Add Key input transformation
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 24 commits contributed to the release.
+ - 2 days passed between releases.
+ - 1 commit was understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Allow choosing the backend for the dashboard example, but people also have to chose is now ([`4841a8e`](https://github.com/byron/prodash/commit/4841a8ed3f949797990b0ad3a6148af4beb7203d))
+ - Release notes for v6.0 ([`403d5a0`](https://github.com/byron/prodash/commit/403d5a0af5fbd072b876c61d59cbaba8c261b7df))
+ - Fix crosstermion docs ([`71e1eca`](https://github.com/byron/prodash/commit/71e1eca154ccce82a012932c6b5f47cea57f4b9b))
+ - Make feature combinations for properly, but… ([`93a069a`](https://github.com/byron/prodash/commit/93a069a2e6d9757f6ca2b77300f8c7d7baf091d4))
+ - Cleanup; allow 'input-thread' and 'input-async' at the same time ([`93a30e0`](https://github.com/byron/prodash/commit/93a30e0baeeb59ea1b8f38e4a23c080c3942525f))
+ - Cleaner input handling using 'input-threaded' and 'input-async' ([`3d11e90`](https://github.com/byron/prodash/commit/3d11e9040510ccbc43057432c9e537c49fd8025d))
+ - Support for async crossterm in crosstermion ([`1a61453`](https://github.com/byron/prodash/commit/1a61453dd6e8f4c2eef7695e1e92285d318e9d9e))
+ - Use stream trait instead of channel directly to abstract a little ([`78e810d`](https://github.com/byron/prodash/commit/78e810d4ee0cb691cec3666f4c585fffd6d6481b))
+ - Don't continue the loop if sending fails… ([`e93fe6e`](https://github.com/byron/prodash/commit/e93fe6ebdf17abf38bba3e00a4c26ff822d42219))
+ - Switch from smol to futures-executor, it's probably cheaper for what we need ([`d7deeb7`](https://github.com/byron/prodash/commit/d7deeb71983d08cb5998e22f23482a0776b7fb2f))
+ - Cleanup, remove unused deps ([`050a821`](https://github.com/byron/prodash/commit/050a821a4f6d70ba624afdd48b744a62cb374254))
+ - Fix prodash build after function renmae ([`3848635`](https://github.com/byron/prodash/commit/3848635432177f5cbc7ac9f18e7c9deadfeaf588))
+ - Docs for crosstermion ([`3429327`](https://github.com/byron/prodash/commit/342932702dfc2ce359548eef72ff86d0b9030fbb))
+ - Enforce testing termion on linux ([`0cce091`](https://github.com/byron/prodash/commit/0cce091c56fcda36a1ca5eb918bb67e48db6f2d9))
+ - Prodash now uses `crosstermion` ([`97ad454`](https://github.com/byron/prodash/commit/97ad45420ad569682e3ee62d7bb7c28fe287b9e3))
+ - Bundle-features and feature documentation ([`b66c413`](https://github.com/byron/prodash/commit/b66c4133fdec9b3cfc5a290898de9ce71f22e10c))
+ - Marry raw mode with the alternative terminal, not with the creating a new tui terminal. ([`f1c734f`](https://github.com/byron/prodash/commit/f1c734f92150eff728444042b6c7cbac90989be9))
+ - Functionality for Alternate screens ([`dcc92ab`](https://github.com/byron/prodash/commit/dcc92ab05599800940359ac6c37d1e70e15c82f7))
+ - Initial terminal implementation, more generic, for tui crossterm ([`e7c07be`](https://github.com/byron/prodash/commit/e7c07be5d93feecc402d0c0793e4a3653c639651))
+ - Add everything required to build with stream support ([`2d60cc0`](https://github.com/byron/prodash/commit/2d60cc0149f80197b865312fab84575993ef5df0))
+ - Add input stream functions ([`21f4147`](https://github.com/byron/prodash/commit/21f41475e4c07539da49c370f37bd9c73611e94b))
+ - Refactor ([`42b8374`](https://github.com/byron/prodash/commit/42b83740c63a928ff3e067fef417d574b1186656))
+ - Add Key input transformation ([`bbf2651`](https://github.com/byron/prodash/commit/bbf2651e379b5758d53a889d9fb220c616d2a096))
+ - Initial version of crosstermium ([`25c8a98`](https://github.com/byron/prodash/commit/25c8a986ffb96e7c5783478c796c849a258c2ae0))
+</details>
+
+## v5.0.0 (2020-07-03)
+
+Support for windows by using Crossbeam by default.
+A first low-effort move to the latest version should be to set the dependency to
+`default-features = false, features = ["render-tui", "render-tui-termion", "localtime", "log-renderer"]`
+to get the same configuration as before.
+
+To try crossbeam, use `with-crossbeam` instead of `render-tui-termion`.
+
+If you have been using the event stream to send your own keys, swap `termion::event::Key` with `prodash::tui::input::Key`.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 16 commits contributed to the release.
+ - 2 days passed between releases.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Major release: windows support ([`656cb6a`](https://github.com/byron/prodash/commit/656cb6a52625ce5c6bb0e35e84f077b1c2b2243b))
+ - Make with-crossbeam the default for maximum compatibility ([`9b8be9e`](https://github.com/byron/prodash/commit/9b8be9e41db9ecf8dd6509e081aa3d26863839f6))
+ - Fix makefile check; remove termion from dev dependenices ([`aa5827d`](https://github.com/byron/prodash/commit/aa5827d5988adf931f499b95df092713ab97a2b1))
+ - Make runtime into compile time error, nice! ([`7fc5961`](https://github.com/byron/prodash/commit/7fc5961d8a7059011f0c9774e5ec10a6240df672))
+ - Refactor; actually try compiling on windows ([`52f2592`](https://github.com/byron/prodash/commit/52f2592461d6289b944d4dee339eb60cec46e0c9))
+ - Refactor ([`72fe1bb`](https://github.com/byron/prodash/commit/72fe1bbd8dcbc79424f40f47e487fb1be5f9b8b6))
+ - Refactor ([`ababea5`](https://github.com/byron/prodash/commit/ababea51c0bddf82d634fc3d6e0e7d3448d074dc))
+ - Fix crossterm input handling ([`8547c8b`](https://github.com/byron/prodash/commit/8547c8bc4489c5934653757250b2735102ea5493))
+ - First crossterm input event loop, but looks like keys are not converted properly ([`9e74ada`](https://github.com/byron/prodash/commit/9e74ada3fd730ff90110bad813fffa4f1c86a683))
+ - Implement crossterm buffer creation (alternate + raw) ([`eaf904b`](https://github.com/byron/prodash/commit/eaf904bfb1014f94bc0422ef167c671bf0b1794c))
+ - Fix accidental import ([`659065d`](https://github.com/byron/prodash/commit/659065d7f44439104dff6c8128c0ed7a4b08aca5))
+ - And map crossterm modifiers on a best-effort basis ([`bafb189`](https://github.com/byron/prodash/commit/bafb189d3d200d57edcc9c7936e3ac37fb6640a0))
+ - First stab as crossterm key event mapping ([`6650a36`](https://github.com/byron/prodash/commit/6650a368df002c2548b926cc72852541d9f3fd46))
+ - Document all tui-renderer toggles and test termion feature toggles ([`27ccdc1`](https://github.com/byron/prodash/commit/27ccdc188d4431a88ea8be0248a9dcc755c9eaa8))
+ - Allow graceful failure if no backend is chosen. ([`1f36d96`](https://github.com/byron/prodash/commit/1f36d9644983cea6c465cdc290d6c1f8d3e3b2f0))
+ - Put all usage of termion behind a feature flag and unify Key input ([`3a1dc75`](https://github.com/byron/prodash/commit/3a1dc75a0c4ca3f5b888c68de91b420f261837dd))
+</details>
+
+## v4.1.0 (2020-07-01)
+
+Allow the TUI to automatically stop if there is no progress to display.
+
+This way, it's easier to use `prodash::tui` for visualizing finite tasks, which originally it wasn't intended for.
+
+Previously, in order to achieve the same, one would have to initialize the TUI with an event stream and send the Event
+for shutting down once the task at hand is complete.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 2 commits contributed to the release.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Bump minor version ([`6755f32`](https://github.com/byron/prodash/commit/6755f326f852b1cf9a8ec64d31adc7bbb3fdcaa7))
+ - Allow the TUI to automatically stop if there is no progress to display ([`4fb6079`](https://github.com/byron/prodash/commit/4fb607994b72dcf17965b0424f495ef9d0875400))
+</details>
+
+## v4.0.5 (2020-07-01)
+
+Fix delayed reset of the terminal.
+
+Previously even after the future was dropped, it seemed like the terminal wasn't reset and the user was required
+to explicitly flush stdout to make the changes appear. This is due to the flushing previously happening too early,
+that is, before the `terminal` was dropped which emits the respective terminal escape codes at this time.
+
+Now the terminal instance is dropped explicitly right before emitting a flush.
+One might argue that the flush should happen in the terminal instance itself, but fixing that is out of scope.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 1 commit contributed to the release.
+ - 1 day passed between releases.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Bump patch level ([`f8b06a6`](https://github.com/byron/prodash/commit/f8b06a6a298928caaf2a6e6e9b2e3135d0ae443b))
+</details>
+
+## v4.0.4 (2020-06-29)
+
+- Simplify `message()` trait bounds
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 2 commits contributed to the release.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Simplify traitbounds; bump patch level ([`be4240e`](https://github.com/byron/prodash/commit/be4240e94aacdde26b58d68ce7550c3e1aaa0094))
+ - (cargo-release) start next development iteration 4.0.4-alpha.0 ([`929225d`](https://github.com/byron/prodash/commit/929225dee1e2c8936a27d127765019ecbc9ee1ad))
+</details>
+
+## v4.0.3 (2020-06-29)
+
+- Remove piper in favor of futures-channel (which was included anyway)
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 4 commits contributed to the release over the course of 33 calendar days.
+ - 42 days passed between releases.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Remove async-channel in favor of (anyway imported) futures-channel ([`e4c2501`](https://github.com/byron/prodash/commit/e4c250106c2f1d81a88793c7ee74f74f21b35d68))
+ - Prepare next release ([`9879999`](https://github.com/byron/prodash/commit/9879999f66bccad3e4d43173bbaa5cc9a5126417))
+ - Update dependencies ([`73c25c2`](https://github.com/byron/prodash/commit/73c25c2ca1d95188543574ab1490961f69d001cf))
+ - Optimize include directive with 'cargo diet' ([`2978d2a`](https://github.com/byron/prodash/commit/2978d2a40b5d2a421029f7838515857cbfb45f08))
+</details>
+
+## v4.0.2 (2020-05-17)
+
+- Upgrade to latest TUI and TUI-react crates
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 1 commit contributed to the release.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Upgrade tui-* crates; bump patch level ([`7b796d1`](https://github.com/byron/prodash/commit/7b796d14cc5d2c9ceb3118e0cbfa3e3abfa86668))
+</details>
+
+## v4.0.1 (2020-05-17)
+
+- Reduce theoretical direct dependencies by not using 'futures' crate directly
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 2 commits contributed to the release.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Bump patch level ([`bf4fa4f`](https://github.com/byron/prodash/commit/bf4fa4f2fdea700f1ce254518d6791348dbe9a45))
+ - Reduce amount of dependencies by 5 ([`03970db`](https://github.com/byron/prodash/commit/03970db3d56a6ac960e95f6c67b8e4250be86791))
+</details>
+
+## v4.0.0 (2020-05-17)
+
+Switch from futures executor to smol.
+
+This actually simplifies some parts of the implementation, while fixing issues along futures not being dropped while they
+were on a thread pool. Now, for the example, no threadpool is used anymore.
+
+**Note** that this also means that in order for each frame to be drawn, one would have to invoke `smol::run` in one thread to
+activate the reactor which processes the timeout/ticker. Alternatively, one would send `Tick` events through a channel to trigger
+a redraw manually.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 1 commit contributed to the release.
+ - 12 days passed between releases.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Replace futures-timer with smol; major version bump ([`ecb7dc8`](https://github.com/byron/prodash/commit/ecb7dc8e925c4acea08908c5c61e453d553ceec7))
+</details>
+
+## v3.6.3 (2020-05-05)
+
+- Fix out-of-bounds access (and panic) due to new and more precise progress bars
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 2 commits contributed to the release.
+ - 1 day passed between releases.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Bump patch level ([`2803e46`](https://github.com/byron/prodash/commit/2803e4698bb01fea2ff06a67af7b7db5e87da564))
+ - A precaution to make out-of-bounds access impossible ([`205a0ef`](https://github.com/byron/prodash/commit/205a0ef418fa5ec645191eecdf5adfe47f0051df))
+</details>
+
+## v3.6.2 (2020-05-03)
+
+- More horizontally precise progress bars; progress bars are now have lines between them vertically
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 5 commits contributed to the release over the course of 21 calendar days.
+ - 24 days passed between releases.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Bump patch level; fix crash due to integer underflow/overlow ([`af60c44`](https://github.com/byron/prodash/commit/af60c44ad27eab2cc0bdcda7b37b2df1c1176f48))
+ - More fine-grained progress bars, both horizontally and vertically ([`d6b7b3f`](https://github.com/byron/prodash/commit/d6b7b3f23215d281b8cecf64423036ddb3ae9081))
+ - Add clippy and fmt lints to actions ([`c56825c`](https://github.com/byron/prodash/commit/c56825cb46233289f8e638f3b7b941fcf09e8592))
+ - Bye bye travis, it was a good time! ([`d29fe5c`](https://github.com/byron/prodash/commit/d29fe5cbfaec8e492bda9c5a6821c88f5b227b41))
+ - Add github actions to compile and test ([`9efa70c`](https://github.com/byron/prodash/commit/9efa70c966446f9393b9495102ea892e4c3a989e))
+</details>
+
+## v3.6.1 (2020-04-09)
+
+- Properly respond to state changes even when 'redraw_only_on_state_change' is enabled
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 1 commit contributed to the release.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Patch bump; redraw also when the state changes, otherwise TUI feels laggy ([`93fce71`](https://github.com/byron/prodash/commit/93fce7193d4fc926ab0d31c868051c22b7258ece))
+</details>
+
+## v3.6.0 (2020-04-09)
+
+- A TUI option to only redraw if the progress actually changed. Useful if the change rate is lower than the frames per second.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 2 commits contributed to the release.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Bump minor ([`f209f60`](https://github.com/byron/prodash/commit/f209f6034492422550d2de24fbcecdee71a370cf))
+ - Add capability to redraw only on state change ([`5fa836f`](https://github.com/byron/prodash/commit/5fa836fa8f035e73c0e4ee3174725ef28c2a93a0))
+</details>
+
+## v3.5.1 (2020-04-09)
+
+- Don't copy messages if the message pane is hidden, saving time
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 1 commit contributed to the release.
+ - 5 days passed between releases.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Bump patch level; copy messages only if the message pane is shown ([`353763b`](https://github.com/byron/prodash/commit/353763bfd588285e1e3770ea9b52a0a1fe179837))
+</details>
+
+## v3.5.0 (2020-04-03)
+
+- Cleaner visuals for hierarchical progress items, these won't show lines if there are no direct children with progress
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 3 commits contributed to the release.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Bump minor ([`39fbc16`](https://github.com/byron/prodash/commit/39fbc16a592703bd6d946a5ee5e2d1a48156042d))
+ - Don't show lines if these nodes are not actually children with progress ([`058b73a`](https://github.com/byron/prodash/commit/058b73a1f603e1538ba24172ce0715bfc0048b4a))
+ - Fix badge ([`97df7da`](https://github.com/byron/prodash/commit/97df7da752b09578aa7adad0d7c2d1866e6570f3))
+</details>
+
+## v3.4.1 (2020-04-02)
+
+- Enable localtime support by default
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 3 commits contributed to the release.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Bump minor - enable localtime by default ([`662f186`](https://github.com/byron/prodash/commit/662f186562423de7b955e24ba8432ea70ae6092b))
+ - Enable localtime by default - pulling time in isn't all that bad ([`699beba`](https://github.com/byron/prodash/commit/699bebaa9028602cd082f2b85f6531730db8e22b))
+ - Update asciinema video ([`0f07b68`](https://github.com/byron/prodash/commit/0f07b68ee60f1c69b3ea64f9d7792114fd5a293b))
+</details>
+
+## v3.4.0 (2020-04-02)
+
+- Even nicer tree rendering, along with screen space savings
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 24 commits contributed to the release over the course of 2 calendar days.
+ - 2 days passed between releases.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Bump minor ([`bbdc625`](https://github.com/byron/prodash/commit/bbdc6252130c05770b6524222435e951e2ded031))
+ - Better orphan display, with dots leading up to the node's level ([`4d69c34`](https://github.com/byron/prodash/commit/4d69c3436ae0e1d5345adeff7ca8cdf575961fa5))
+ - Use space much more efficiently in the tree view as well ([`3430a8d`](https://github.com/byron/prodash/commit/3430a8d46e251b166aba70dd078ce914de845dbf))
+ - Omit line between tree on left and progress bars to safe space ([`a3e5fe5`](https://github.com/byron/prodash/commit/a3e5fe5d2489028bc764fc0e771707419176a957))
+ - Oh my, that was sooooo worth it! ([`31bb906`](https://github.com/byron/prodash/commit/31bb906c643c67d95735b22d0aba0d883425036e))
+ - Looks pretty good already, now the tweaking ([`7f16472`](https://github.com/byron/prodash/commit/7f164723dca2723c9e37fd4773820576049f4f6d))
+ - Make orphan nodes work ([`497fbce`](https://github.com/byron/prodash/commit/497fbcee0d7ceda90eafbf3800d8ca68f6da0c3a))
+ - All tests green for the first time! ([`edf2024`](https://github.com/byron/prodash/commit/edf20248be59998553a47a70a764a64fb69b482c))
+ - Maybe a tiny step closer, definitely better/fixed index handling ([`42b31da`](https://github.com/byron/prodash/commit/42b31da9262c6842243a998bb98960b5786b9b1e))
+ - Before it all goes down the drain - only one thing to fix - let's checkpoint it ([`83a89ee`](https://github.com/byron/prodash/commit/83a89ee194db870bf267bf12f6ee73daaff1502d))
+ - I think the problem is that I try to squash a hiarchy level. Let's not do that ([`3c77eab`](https://github.com/byron/prodash/commit/3c77eab4152683babc1921022397ece103e1a790))
+ - Is this ever going to work? ([`8236d55`](https://github.com/byron/prodash/commit/8236d557eb03179e382107bc3cdb40cb1c488aba))
+ - A little closer, but needs way more tests :D ([`14876ee`](https://github.com/byron/prodash/commit/14876ee3fd5f236da9d8811096400f1728a31e3f))
+ - Refactor ([`8f6061f`](https://github.com/byron/prodash/commit/8f6061ffa75d18da2ac51d701700a90adc3b71b6))
+ - Better, but only works correctly for a single level - more nesting required Please enter the commit message for your changes. Lines starting ([`4083c4e`](https://github.com/byron/prodash/commit/4083c4e77fce5b12adb837438fb43ba7de98b642))
+ - Now we are actually closing in for this to work…OMG! ([`e9d268f`](https://github.com/byron/prodash/commit/e9d268f6ef670ec3c4428166d22e30c74d5eaab8))
+ - A little better ([`15a2b02`](https://github.com/byron/prodash/commit/15a2b02110135326598699733ba127a905aade2b))
+ - Add first tests for adjacency computation - it's not exactly working out of the box *:D ([`4e033b4`](https://github.com/byron/prodash/commit/4e033b4f20cca4d23ac23a3a42fd13ed50f332d5))
+ - Add everything needed to make use of the new system, and it's totally not working :D ([`40690b6`](https://github.com/byron/prodash/commit/40690b66f70ad96feddf92b6377838abeec76dbd))
+ - This might even work…one day :D ([`4652654`](https://github.com/byron/prodash/commit/46526549cb1392dc37552effa0837f146116d69c))
+ - Getting there, slowly, need refactor ([`46d015c`](https://github.com/byron/prodash/commit/46d015cbc3de26f54185f78fa0af8f90054f08bf))
+ - First sketch on getting an adjecency map by searching a sorted list of entries ([`0838165`](https://github.com/byron/prodash/commit/08381650f157f9430d7155d3543cb0bdfeab4864))
+ - Draw progress lines without ellipsis; make clearer that this happens ([`678fbaa`](https://github.com/byron/prodash/commit/678fbaa085ea0680f7f2212d27e6a577f65091b4))
+ - Improve tree drawing further ([`adc49c6`](https://github.com/byron/prodash/commit/adc49c624b21b6e200c4d00e29406c1262fd0c61))
+</details>
+
+## v3.3.0 (2020-03-31)
+
+- Much nicer task tree visualization
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 6 commits contributed to the release over the course of 1 calendar day.
+ - 2 days passed between releases.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Bump minor ([`cf9ddb8`](https://github.com/byron/prodash/commit/cf9ddb89414a844120b60b2a5d004a411f73cf49))
+ - Much nicer display of trees, now also on the level of progress bars ([`706d658`](https://github.com/byron/prodash/commit/706d6589b23b0df3f634017f53189074997d38f5))
+ - Refactor ([`536b4c8`](https://github.com/byron/prodash/commit/536b4c878206f9c6e6fcf443bae701af628cb0d4))
+ - A nicer way to draw the progress - show the tree even in the progress bars ([`840bb3a`](https://github.com/byron/prodash/commit/840bb3a58099b13086fe325631d8858f6546252b))
+ - Much better tree display, even though the code doing it is a bit wonky :D ([`833bb98`](https://github.com/byron/prodash/commit/833bb982eb76ead49e86f021537a66a2af404b4b))
+ - Use all functionality that now is available in tui-react ([`f5b1ee1`](https://github.com/byron/prodash/commit/f5b1ee109be8000bdae1dd036d82846ceaeb905d))
+</details>
+
+## v3.2.0 (2020-03-28)
+
+- Application can control if the GUI will respond to interrupt requests
+
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 2 commits contributed to the release.
+ - 2 days passed between releases.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Bump minor; dont' discard previous value of 'interrupt requested' ([`be1ce75`](https://github.com/byron/prodash/commit/be1ce7526ba6d8abf85371f6685664390835ba01))
+ - Visualize the interrupt-requested state, while providing useful information ([`273a1a6`](https://github.com/byron/prodash/commit/273a1a62bf29a6562c246079c3deeb182cdf4ebe))
+</details>
+
+## v3.1.1 (2020-03-25)
+
+- Bugfix (really): Finally delayed column resizing works correctly.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 1 commit contributed to the release.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Bump patch - fix column sizing for good… ([`5029823`](https://github.com/byron/prodash/commit/50298236bfd32de329862540bdd00e1e1fae9fec))
+</details>
+
+## v3.1.0 (2020-03-25)
+
+- Tree::halted(…) indicates interruptable tasks without progress. Tree::blocked(…) means non-interruptable without progress.
+
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 1 commit contributed to the release.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Support for 'halted' state to indicate interruptible tasks; bump minor ([`b5ef271`](https://github.com/byron/prodash/commit/b5ef2716b52ecf145c0aff458a05f44b0792ce7a))
+</details>
+
+## v3.0.2 (2020-03-25)
+
+- Bugfix: Allow column-width computation to recover from becoming 0
+
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 1 commit contributed to the release.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Allow column width computation to recover from 0; bump patch ([`856b7ee`](https://github.com/byron/prodash/commit/856b7ee3206d60cb9038f06822515fba0e23688d))
+</details>
+
+## v3.0.1 (2020-03-25)
+
+<csr-id-82baf266045d44ba31aad4e570c687d7c51d0df7/>
+
+- Bugfix: Don't allow values of 0 for when to recompute task column widths
+
+
+### Other
+
+ - <csr-id-82baf266045d44ba31aad4e570c687d7c51d0df7/> assure we never try to do 'x % 0' :D
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 1 commit contributed to the release.
+ - 1 commit was understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Assure we never try to do 'x % 0' :D ([`82baf26`](https://github.com/byron/prodash/commit/82baf266045d44ba31aad4e570c687d7c51d0df7))
+</details>
+
+## v3.0.0 (2020-03-25)
+
+- New TUI option to delay computation of column width for stability with rapidly changing tasks
+
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 4 commits contributed to the release.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Bump version to 3.0.0 ([`bc46acc`](https://github.com/byron/prodash/commit/bc46accd27868ad3bbb21321bee0af114d185fce))
+ - Allow to control column width recomputation ([`d4dc966`](https://github.com/byron/prodash/commit/d4dc966dc5ae1d907f7b830a03477f84baead855))
+ - Support to compute column size only every so many ticks… ([`98e0f63`](https://github.com/byron/prodash/commit/98e0f630ffe858b989e7c973de1556a4477a0c8f))
+ - Example dashboard: Make sure ticker for context does not outpace FPS ([`c25217b`](https://github.com/byron/prodash/commit/c25217b54fd23d2c450211763a16270f39c3efdb))
+</details>
+
+## v2.1.0 (2020-03-24)
+
+- Optional cargo feature "localtime" shows all times in the local timezone
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 2 commits contributed to the release.
+ - 1 day passed between releases.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Add cargo feature 'localtime' to show all dates in localtime ([`a87f8cd`](https://github.com/byron/prodash/commit/a87f8cd0e9b9ea70a4a50b954ec3aa3160f2b3ab))
+ - Make chrono dependency available via re-exports ([`a5d4423`](https://github.com/byron/prodash/commit/a5d44233c56a9a7e05050e95c1aeaf591546bdcb))
+</details>
+
+## v2.0.1 (2020-03-23)
+
+- fix integer underflow with graphemes that report width of 0
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 2 commits contributed to the release over the course of 1 calendar day.
+ - 15 days passed between releases.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Fix integer underflow with graphemes of width 0 ([`d9808cd`](https://github.com/byron/prodash/commit/d9808cd518b80fb4032a9176f1b1ec4dc5a5fdef))
+ - Transfer to new github location ([`6fa2e8d`](https://github.com/byron/prodash/commit/6fa2e8dc1b795c5614882e95e184810f7f1e466e))
+</details>
+
+## v2.0.0 (2020-03-07)
+
+* BREAKING: `progress.blocked(eta)` now takes a statically known reason for the blocked state `progress.blocked(reason, eta)`. This is
+ useful to provide more context.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 1 commit contributed to the release.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Reasons for block states; major version bump ([`74abaeb`](https://github.com/byron/prodash/commit/74abaeb4486a3a7b6889c3ed99244ab2c1b0bbf7))
+</details>
+
+## v1.2.0 (2020-03-07)
+
+* Support for eta messages in blocked unbounded tasks
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 1 commit contributed to the release.
+ - 4 days passed between releases.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - ETA messages in blocked unbounded tasks ([`d42c0d5`](https://github.com/byron/prodash/commit/d42c0d52f803595a48594a91b3278ce57ff8b95b))
+</details>
+
+## v1.1.6 (2020-03-02)
+
+* improve API symmetry by providing a `Tree::name()` to accompany `Tree::set_name(…)`
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 2 commits contributed to the release over the course of 5 calendar days.
+ - 8 days passed between releases.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Improve API symmetry ([`759f1f4`](https://github.com/byron/prodash/commit/759f1f4164f21804336f2c5677322b59c4218f7a))
+ - Put changelog into lib.rs, where it already was :) ([`dfc810b`](https://github.com/byron/prodash/commit/dfc810b7b012e536353ceeed61f2ca7ed935930d))
+</details>
+
+## v1.1.5 (2020-02-23)
+
+* Flush stdout when the TUI stopped running. That way, the alternate/original screen will be shown right away.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 1 commit contributed to the release.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - V1.1.5 - flush stdout right away ([`8734668`](https://github.com/byron/prodash/commit/87346682d74951ed332a2533c00232095cc1d40b))
+</details>
+
+## v1.1.4 (2020-02-23)
+
+* Don't pretend to use &str if in fact an owned string is required. This caused unnecessary clones for those who pass owned strings.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 3 commits contributed to the release.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Version bump ([`15d9681`](https://github.com/byron/prodash/commit/15d9681acd734c8518a541f621ccc69c24d9a0f3))
+ - Cargo clippy ([`3adba17`](https://github.com/byron/prodash/commit/3adba17c71b8e98634cf651219ce247b2182174e))
+ - Don't pretend we only need str ([`d05158b`](https://github.com/byron/prodash/commit/d05158b135bab195dc8a34a573cee120079146b2))
+</details>
+
+## v1.1.3 (2020-02-23)
+
+* hide cursor or a nicer visual experience
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 3 commits contributed to the release.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Hide cursor, bump version ([`ecf5cc4`](https://github.com/byron/prodash/commit/ecf5cc4a55536d36290b8bea1f816d3d29bb5468))
+ - Run the few doc tests we have ([`9b28cc0`](https://github.com/byron/prodash/commit/9b28cc0fce171bbadf736d25d8aec1614f24caf7))
+ - (cargo-release) start next development iteration 1.1.3-alpha.0 ([`cae0e02`](https://github.com/byron/prodash/commit/cae0e024d017d84962dc459e200d361c372f9c89))
+</details>
+
+## v1.1.2 (2020-02-22)
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 5 commits contributed to the release.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Show travis badge ([`65b48f1`](https://github.com/byron/prodash/commit/65b48f1270310453e7b03363ddfdad5551aa6900))
+ - Bump version for new crate meta-data ([`c557b6b`](https://github.com/byron/prodash/commit/c557b6b05206822e2b01feb079d2b8745b408019))
+ - Travis support ([`507cc20`](https://github.com/byron/prodash/commit/507cc207742b536a0bd92a4357f6cfc1b5b2126b))
+ - Adjust repository link ([`d75d91f`](https://github.com/byron/prodash/commit/d75d91fe16e32747bb6400cbe3b3e63c4399f123))
+ - Initial commit, as copied from cli ([`708901b`](https://github.com/byron/prodash/commit/708901b796110bd49bcac6bc1ef1daa7b1931720))
+</details>
+
+## v1.1.0
+
+* fix toggles - previously prodash, withoug tui, would always build humantime and unicode width
+* add support for logging as user interface
+
+## v0.7.0 (2021-05-02)
+
+## v0.6.0 (2021-01-04)
+
+## v0.5.0 (2020-11-15)
+
+## v0.4.0 (2020-09-28)
+
+## v0.3.2 (2020-09-14)
+
+## v0.3.1 (2020-09-13)
+
+## v0.3.0 (2020-07-22)
+
+## v0.2.0 (2020-07-11)
+
+## v0.1.4 (2020-07-06)
+
+## v0.1.3 (2020-07-06)
+
+## v0.1.2 (2020-07-06)
+
+## v0.1.1 (2020-07-05)
+
diff --git a/vendor/prodash/Cargo.lock b/vendor/prodash/Cargo.lock
new file mode 100644
index 000000000..ee40434ae
--- /dev/null
+++ b/vendor/prodash/Cargo.lock
@@ -0,0 +1,1212 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "anes"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
+
+[[package]]
+name = "ansi_term"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "argh"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab257697eb9496bf75526f0217b5ed64636a9cfafa78b8365c71bd283fcef93e"
+dependencies = [
+ "argh_derive",
+ "argh_shared",
+]
+
+[[package]]
+name = "argh_derive"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b382dbd3288e053331f03399e1db106c9fb0d8562ad62cb04859ae926f324fa6"
+dependencies = [
+ "argh_shared",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "argh_shared"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64cb94155d965e3d37ffbbe7cc5b82c3dd79dd33bd48e536f73d2cfb8d85506f"
+
+[[package]]
+name = "async-channel"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833"
+dependencies = [
+ "concurrent-queue",
+ "event-listener",
+ "futures-core",
+]
+
+[[package]]
+name = "async-executor"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b"
+dependencies = [
+ "async-lock",
+ "async-task",
+ "concurrent-queue",
+ "fastrand",
+ "futures-lite",
+ "slab",
+]
+
+[[package]]
+name = "async-io"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c374dda1ed3e7d8f0d9ba58715f924862c63eae6849c92d3a18e7fbde9e2794"
+dependencies = [
+ "async-lock",
+ "autocfg",
+ "concurrent-queue",
+ "futures-lite",
+ "libc",
+ "log",
+ "parking",
+ "polling",
+ "slab",
+ "socket2",
+ "waker-fn",
+ "windows-sys 0.42.0",
+]
+
+[[package]]
+name = "async-lock"
+version = "2.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7"
+dependencies = [
+ "event-listener",
+]
+
+[[package]]
+name = "async-task"
+version = "4.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524"
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "debc29dde2e69f9e47506b525f639ed42300fc014a3e007832592448fa8e4599"
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "blocking"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c67b173a56acffd6d2326fb7ab938ba0b00a71480e14902b2591c87bc5741e8"
+dependencies = [
+ "async-channel",
+ "async-lock",
+ "async-task",
+ "atomic-waker",
+ "fastrand",
+ "futures-lite",
+]
+
+[[package]]
+name = "bytesize"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38fcc2979eff34a4b84e1cf9a1e3da42a7d44b3b690a40cdcb23e3d556cfb2e5"
+
+[[package]]
+name = "cassowary"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
+
+[[package]]
+name = "cast"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
+
+[[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 = "ciborium"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f"
+dependencies = [
+ "ciborium-io",
+ "ciborium-ll",
+ "serde",
+]
+
+[[package]]
+name = "ciborium-io"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369"
+
+[[package]]
+name = "ciborium-ll"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b"
+dependencies = [
+ "ciborium-io",
+ "half",
+]
+
+[[package]]
+name = "clap"
+version = "3.2.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5"
+dependencies = [
+ "bitflags",
+ "clap_lex",
+ "indexmap",
+ "textwrap",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
+dependencies = [
+ "os_str_bytes",
+]
+
+[[package]]
+name = "compound_duration"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c803d816c4ed6d0dadd5b54f7ef4f3761418fe802106b161d77476cc3c664c"
+
+[[package]]
+name = "concurrent-queue"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "criterion"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb"
+dependencies = [
+ "anes",
+ "atty",
+ "cast",
+ "ciborium",
+ "clap",
+ "criterion-plot",
+ "itertools",
+ "lazy_static",
+ "num-traits",
+ "oorandom",
+ "regex",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "tinytemplate",
+ "walkdir",
+]
+
+[[package]]
+name = "criterion-plot"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
+dependencies = [
+ "cast",
+ "itertools",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossterm"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67"
+dependencies = [
+ "bitflags",
+ "crossterm_winapi",
+ "futures-core",
+ "libc",
+ "mio",
+ "parking_lot",
+ "signal-hook",
+ "signal-hook-mio",
+ "winapi",
+]
+
+[[package]]
+name = "crossterm_winapi"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "crosstermion"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99aabd9b02c2d5f72697f30ffb46f5a9ff4bd240d826049892cf62c31daeed04"
+dependencies = [
+ "ansi_term",
+ "async-channel",
+ "crossterm",
+ "futures-channel",
+ "futures-core",
+ "futures-lite",
+ "termion",
+ "tui",
+ "tui-react",
+]
+
+[[package]]
+name = "ctrlc"
+version = "3.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbcf33c2a618cbe41ee43ae6e9f2e48368cd9f9db2896f10167d8d762679f639"
+dependencies = [
+ "nix",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "dashmap"
+version = "5.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc"
+dependencies = [
+ "cfg-if",
+ "hashbrown",
+ "lock_api",
+ "once_cell",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "either"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
+
+[[package]]
+name = "env_logger"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0"
+dependencies = [
+ "humantime",
+ "log",
+]
+
+[[package]]
+name = "event-listener"
+version = "2.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
+
+[[package]]
+name = "fastrand"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
+dependencies = [
+ "instant",
+]
+
+[[package]]
+name = "futures"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531"
+
+[[package]]
+name = "futures-lite"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48"
+dependencies = [
+ "fastrand",
+ "futures-core",
+ "futures-io",
+ "memchr",
+ "parking",
+ "pin-project-lite",
+ "waker-fn",
+]
+
+[[package]]
+name = "futures-macro"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364"
+
+[[package]]
+name = "futures-task"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366"
+
+[[package]]
+name = "futures-util"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1"
+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.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "half"
+version = "1.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "human_format"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86cce260d758a9aa3d7c4b99d55c815a540f8a37514ba6046ab6be402a157cb0"
+
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
+[[package]]
+name = "indexmap"
+version = "1.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
+
+[[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.139"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
+
+[[package]]
+name = "lock_api"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "memchr"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+
+[[package]]
+name = "mio"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
+dependencies = [
+ "libc",
+ "log",
+ "wasi",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "nix"
+version = "0.26.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "libc",
+ "static_assertions",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_threads"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "numtoa"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
+
+[[package]]
+name = "once_cell"
+version = "1.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
+
+[[package]]
+name = "oorandom"
+version = "11.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
+
+[[package]]
+name = "os_str_bytes"
+version = "6.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
+
+[[package]]
+name = "parking"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "polling"
+version = "2.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22122d5ec4f9fe1b3916419b76be1e80bcb93f618d071d2edf841b137b2a2bd6"
+dependencies = [
+ "autocfg",
+ "cfg-if",
+ "libc",
+ "log",
+ "wepoll-ffi",
+ "windows-sys 0.42.0",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.51"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "prodash"
+version = "23.1.1"
+dependencies = [
+ "argh",
+ "async-executor",
+ "async-io",
+ "atty",
+ "blocking",
+ "bytesize",
+ "compound_duration",
+ "criterion",
+ "crosstermion",
+ "ctrlc",
+ "dashmap",
+ "env_logger",
+ "futures",
+ "futures-core",
+ "futures-lite",
+ "futures-util",
+ "human_format",
+ "humantime",
+ "log",
+ "once_cell",
+ "parking_lot",
+ "rand",
+ "signal-hook",
+ "time",
+ "tui",
+ "tui-react",
+ "unicode-segmentation",
+ "unicode-width",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
+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 = "redox_syscall"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "redox_termios"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f"
+dependencies = [
+ "redox_syscall",
+]
+
+[[package]]
+name = "regex"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
+dependencies = [
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
+
+[[package]]
+name = "ryu"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "serde"
+version = "1.0.152"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.152"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "signal-hook"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9"
+dependencies = [
+ "libc",
+ "signal-hook-registry",
+]
+
+[[package]]
+name = "signal-hook-mio"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
+dependencies = [
+ "libc",
+ "mio",
+ "signal-hook",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
+
+[[package]]
+name = "socket2"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "termion"
+version = "1.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e"
+dependencies = [
+ "libc",
+ "numtoa",
+ "redox_syscall",
+ "redox_termios",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
+
+[[package]]
+name = "time"
+version = "0.3.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890"
+dependencies = [
+ "itoa",
+ "libc",
+ "num_threads",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
+
+[[package]]
+name = "time-macros"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36"
+dependencies = [
+ "time-core",
+]
+
+[[package]]
+name = "tinytemplate"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "tui"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccdd26cbd674007e649a272da4475fb666d3aa0ad0531da7136db6fab0e5bad1"
+dependencies = [
+ "bitflags",
+ "cassowary",
+ "crossterm",
+ "termion",
+ "unicode-segmentation",
+ "unicode-width",
+]
+
+[[package]]
+name = "tui-react"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "542c37309aaf01ddaea86891f7845a8b0124194c6ccae6dbae7d223752648f4d"
+dependencies = [
+ "log",
+ "tui",
+ "unicode-segmentation",
+ "unicode-width",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
+
+[[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 = "waker-fn"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
+
+[[package]]
+name = "walkdir"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
+dependencies = [
+ "same-file",
+ "winapi",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wepoll-ffi"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-sys"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
diff --git a/vendor/prodash/Cargo.toml b/vendor/prodash/Cargo.toml
new file mode 100644
index 000000000..f3ea2b598
--- /dev/null
+++ b/vendor/prodash/Cargo.toml
@@ -0,0 +1,240 @@
+# 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"
+name = "prodash"
+version = "23.1.1"
+authors = ["Sebastian Thiel <sebastian.thiel@icloud.com>"]
+include = [
+ "src/**/*",
+ "README.md",
+ "LICENSE.md",
+ "CHANGELOG.md",
+]
+description = "A dashboard for visualizing progress of asynchronous and possibly blocking tasks"
+readme = "README.md"
+license = "MIT"
+repository = "https://github.com/Byron/prodash"
+
+[package.metadata.docs.rs]
+all-features = true
+
+[lib]
+doctest = true
+
+[[example]]
+name = "dashboard"
+path = "examples/dashboard.rs"
+required-features = [
+ "render-tui",
+ "render-tui-crossterm",
+ "render-line",
+ "render-line-crossterm",
+ "signal-hook",
+ "render-line-autoconfigure",
+ "progress-tree",
+]
+
+[[example]]
+name = "dashboard-termion"
+path = "examples/dashboard.rs"
+required-features = [
+ "render-tui",
+ "render-tui-termion",
+ "render-line",
+ "render-line-termion",
+ "progress-tree",
+]
+
+[[example]]
+name = "units"
+path = "examples/units.rs"
+required-features = [
+ "unit-bytes",
+ "unit-duration",
+ "unit-human",
+ "render-tui",
+ "render-tui-crossterm",
+ "render-line",
+ "render-line-crossterm",
+ "signal-hook",
+]
+
+[[bench]]
+name = "usage"
+path = "benches/usage.rs"
+harness = false
+
+[dependencies.async-io]
+version = "1.0.0"
+optional = true
+
+[dependencies.atty]
+version = "0.2.14"
+optional = true
+
+[dependencies.bytesize]
+version = "1.0.1"
+optional = true
+
+[dependencies.compound_duration]
+version = "1.2.0"
+optional = true
+
+[dependencies.crosstermion]
+version = "0.10.1"
+optional = true
+default-features = false
+
+[dependencies.ctrlc]
+version = "3.1.4"
+features = ["termination"]
+optional = true
+default-features = false
+
+[dependencies.dashmap]
+version = "5.1.0"
+optional = true
+default-features = false
+
+[dependencies.futures-core]
+version = "0.3.4"
+optional = true
+default-features = false
+
+[dependencies.futures-lite]
+version = "1.5.0"
+optional = true
+
+[dependencies.human_format]
+version = "1.0.3"
+optional = true
+
+[dependencies.humantime]
+version = "2.0.0"
+optional = true
+
+[dependencies.log]
+version = "0.4.8"
+optional = true
+
+[dependencies.parking_lot]
+version = "0.12.1"
+optional = true
+default-features = false
+
+[dependencies.signal-hook]
+version = "0.3.9"
+optional = true
+default-features = false
+
+[dependencies.time]
+version = "0.3.2"
+features = [
+ "std",
+ "local-offset",
+ "formatting",
+]
+optional = true
+default-features = false
+
+[dependencies.tui]
+version = "0.19.0"
+optional = true
+default-features = false
+
+[dependencies.tui-react]
+version = "0.19.0"
+optional = true
+
+[dependencies.unicode-segmentation]
+version = "1.6.0"
+optional = true
+
+[dependencies.unicode-width]
+version = "0.1.7"
+optional = true
+
+[dev-dependencies.argh]
+version = "0.1.3"
+
+[dev-dependencies.async-executor]
+version = "1.1.0"
+
+[dev-dependencies.async-io]
+version = "1.1.0"
+
+[dev-dependencies.atty]
+version = "0.2.14"
+
+[dev-dependencies.blocking]
+version = "1.0.0"
+
+[dev-dependencies.criterion]
+version = "0.4.0"
+default-features = false
+
+[dev-dependencies.env_logger]
+version = "0.10.0"
+features = ["humantime"]
+default-features = false
+
+[dev-dependencies.futures]
+version = "0.3.5"
+
+[dev-dependencies.futures-util]
+version = "0.3.4"
+default-features = false
+
+[dev-dependencies.once_cell]
+version = "1.4.0"
+
+[dev-dependencies.rand]
+version = "0.8.1"
+
+[features]
+default = [
+ "progress-tree",
+ "progress-tree-log",
+]
+local-time = ["time"]
+progress-log = ["log"]
+progress-tree = ["parking_lot"]
+progress-tree-hp-hashmap = ["dashmap"]
+progress-tree-log = ["log"]
+render-line = [
+ "crosstermion/color",
+ "humantime",
+ "unicode-width",
+]
+render-line-autoconfigure = ["atty"]
+render-line-crossterm = ["crosstermion/crossterm"]
+render-line-termion = ["crosstermion/termion"]
+render-tui = [
+ "tui",
+ "unicode-segmentation",
+ "unicode-width",
+ "crosstermion/input-async",
+ "tui-react",
+ "futures-lite",
+ "futures-core",
+ "async-io",
+ "humantime",
+]
+render-tui-crossterm = [
+ "crosstermion/tui-react-crossterm",
+ "crosstermion/input-async-crossterm",
+]
+render-tui-termion = ["crosstermion/tui-react-termion"]
+unit-bytes = ["bytesize"]
+unit-duration = ["compound_duration"]
+unit-human = ["human_format"]
diff --git a/vendor/prodash/LICENSE.md b/vendor/prodash/LICENSE.md
new file mode 100644
index 000000000..3315f8779
--- /dev/null
+++ b/vendor/prodash/LICENSE.md
@@ -0,0 +1,25 @@
+The MIT License (MIT)
+=====================
+
+Copyright © `2020` `Sebastian Thiel`
+
+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.
diff --git a/vendor/prodash/README.md b/vendor/prodash/README.md
new file mode 100644
index 000000000..c9fbbb5ec
--- /dev/null
+++ b/vendor/prodash/README.md
@@ -0,0 +1,120 @@
+![Rust](https://github.com/Byron/prodash/workflows/Rust/badge.svg)
+[![Crates.io](https://img.shields.io/crates/v/prodash.svg)](https://crates.io/crates/prodash)
+
+**prodash** allows to integrate progress reporting into concurrent applications and provides renderers for displaying it in various ways.
+
+It's easy to integrate thanks to a pragmatic API, and comes with a terminal user interface by default.
+
+[![asciicast](https://asciinema.org/a/315956.svg)](https://asciinema.org/a/315956)
+[![asciicast](https://asciinema.org/a/346619.svg)](https://asciinema.org/a/346619)
+
+## How to use…
+
+Be sure to read the documentation at https://docs.rs/prodash, it contains various examples on how to get started.
+
+Or run the demo application like so `cd prodash && cargo run --all-features --example dashboard`.
+
+## Feature Toggles
+
+This crate comes with various cargo features to tailor it to your needs.
+
+* **progress-tree** _(default)_
+ * Provide a `Progress` and `Root` trait implementation for use with the `render-line` and `render-tui` backed by `dashmap`.
+ * **progress-tree-hp-hashmap** - high-performance registry for pregree tree nodes in case of ultra-heavy insertions and deletions.
+ * If this is necessary, it's probably impossible to resonably visualize the progress tree anyway, but the option exists nonetheless in case
+ it is ever needed. Historically, this was the default, but now it seems simpler is better and just fine for typical programs.
+ * **progress-tree-log** _(default)_
+ * If logging in the `log` crate is initialized, a `log` will be used to output all messages provided to
+ `tree::Item::message(…)` and friends. No actual progress is written.
+ * May interfere with `render-tui` or `render-line`, or any renderer outputting to the console.
+* **progress-log**
+ * A `Progress` implementation which logs messages and progress using the `log` crate
+* **local-time**
+ * If set, timestamps in the message pane of the `render-tui` will be using the local time, not UTC
+ * If set, timestamps of the log messages of the `render-line` will be using the local time, not UTC
+ * Has no effect without the `render-tui` or `render-line` respectively
+ * **On Unix** one needs to provide flags to rustc when building the binary to acknowledge potential unsoundness: `RUSTFLAGS="--cfg unsound_local_offset" cargo build`
+ will do the job, but there are [other ways](https://doc.rust-lang.org/cargo/reference/config.html#hierarchical-structure) to do that as well.
+* **render-line**
+ * Provide a minimal line-based progress renderer which can be limited to a subset of the progress hierarchy.
+ * It's like the render-tui, but with far less dependencies and less visual fidelity - all it needs is to move
+ the cursor a little while drawing characters and block graphics.
+ * Support for [clicolors spec](https://bixense.com/clicolors/) and [no-color spec](https://no-color.org)
+ * Supports initial delay that won't affect log messages, showing progress only when needed, automatically.
+ * Requires one of these additional feature flags to be set to be functional
+ * **one required** _(mutually exclusive)_
+ * **render-line-crossterm** - use the _crossterm_ backend, useful for working on windows
+ * **render-line-termion** - use the _termion_ backend, useful for lean unix-only builds
+ * _Optional features_
+ * **render-line-autoconfigure**
+ * If enabled, calls to `render::line::Options::auto_configure()` will configure the display based on whether or not we are in a terminal
+ and set its color mode based on what's possible or desired.
+ * **signal-hook**
+ * If set, and the `hide_cursor` line renderer option is set, the cursor will be hidden **and** *SIG_INT* and *SIG_TERM* handlers will be
+ installed to reset the cursor on exit. Otherwise you have to make sure to call `shutdown_and_wait()` on the `JoinHandle` returned
+ to give the renderer a chance to undo the terminal changes. Failing to do so will leave the cusor hidden once the program has already
+ finished.
+ * Comes at the cost of an extra thread and additional dependencies.
+* **render-tui**
+ * Provide a terminal user interface visualizing every detail of the current progress state. It treats the terminal
+ as a matrix display.
+ * Requires one of these additional feature flags to be set to be functional
+ ** _(one required, mutually exclusive)_
+ * **render-tui-crossterm**
+ * Use the `crossterm` crate as terminal backend
+ * Works everywhere natively, but has more dependencies
+ * You can set additional features like this `cargo build --features render-tui-crossterm,crossterm/event-stream`
+ * **render-tui-termion**
+ * Use the `termion` crate as terminal backend
+ * It has less dependencies but works only on `unix` systems
+ * to get this, disable default features and chose at least `render-tui` and `render-tui-termion`.
+* **unit-bytes**
+ * Supports dynamic byte display using the tiny `bytesize` crate.
+* **unit-human**
+ * Display counts in a way that is easier to grasp for humans, using the tiny `human_format` crate.
+* **unit-duration**
+ * Displays time in seconds like '_5m4s_' using the tiny `compound_duration` crate.
+
+## Features
+
+* fast insertions and updates for transparent progress tracking of highly concurrent programs
+* a messages buffer for information about success and failure
+* a terminal user interface for visualization, with keyboard controls and dynamic re-sizing
+* unicode and multi-width character support
+
+## Limitations
+
+* the *line renderer* is inherently limited in the amount of progress it can display without visual artifacts.
+* it does copy quite some state each time it displays progress information and messages
+* The underlying sync data structure, `dashmap`, does not document every use of unsafe
+ * I also evaluated `evmap`, which has 25% less uses of unsafe, but a more complex interface.
+ * Thus far it seemed 'ok' to use, who knows… we are getting mutable pieces of a hashmap from multiple threads,
+ however, we never hand out multiple handles to the same child which should make actual concurrent access to
+ the same key impossible.
+* If there are more than 2^16 tasks
+ * then
+ * running concurrently on a single level of the tree, they start overwriting each other
+ * over its lifetime, even though they do not run concurrently, eventually new tasks will seem like old tasks (their ID wrapped around)
+ * why
+ * on drop, they never decrement a child count used to generate a new ID
+ * fix
+ * make the id bigger, like u32
+ * we should do that once there is a performance test
+* If the log lines are too long for the terminal width when using the *line renderer*
+ * then
+ * visual artifacts will appear
+ * why
+ * trying to draw beyond the terminal boundary will add a line break automatically, which can cause unexpected overdraw.
+ * **fix**
+ * count amount of blocks drawn, without ansi codes, and stop drawing at the boundary.
+
+## Lessons Learned
+
+* `drop()` is not garantueed to be called when the future returns Ready and is in the futures::executor::ThreadPool
+ * Workaround: drop and cleanup explicitly, prone to forgetting it.
+ * This is also why `futures::future::abortable()` works (by stopping the polling), but doesn't as cleanup is not performed,
+ even though it clearly would be preferred.
+ * fix
+ * Use a join handle and await it - this will drop the future properly
+* `select()` might not work with complex futures - these should then be `boxed()` if `Unpin` isn't implemented.
+
diff --git a/vendor/prodash/src/lib.rs b/vendor/prodash/src/lib.rs
new file mode 100644
index 000000000..974cc8598
--- /dev/null
+++ b/vendor/prodash/src/lib.rs
@@ -0,0 +1,86 @@
+#![deny(unsafe_code, missing_docs)]
+
+/*!
+Prodash is a dashboard for displaying the progress of concurrent application.
+
+It consists of two parts
+
+* a `Tree` to gather progress information and messages
+* a terminal user interface which displays this information, along with optional free-form information provided by the application itself
+
+Even though the `Tree` is not async, it's meant to be transparent and non-blocking performance wise, and benchmarks seem to indicate this
+is indeed the case.
+
+The **terminal user interface** seems to be the least transparent part, but can be configured to refresh less frequently.
+
+# Terminal User Interface
+
+By default, a TUI is provided to visualize all state. Have a look at [the example provided in the tui module](./tui/index.html).
+
+**Please note** that it is behind the `render-tui` feature toggle, which is enabled by default.
+
+# Logging
+
+If the feature `progress-tree-log` is enabled (default), most calls to `progress` will also be logged.
+That way, even without a terminal user interface, there will be progress messages.
+Please note that logging to stdout should not be performed with this feature enabled and a terminal user interface is shown, as this will
+seriously interfere with the TUI.
+
+# A demo application
+
+Please have a look at the [dashboard demo](https://github.com/Byron/crates-io-cli-rs/blob/master/prodash/examples/dashboard.rs).
+
+[![asciicast](https://asciinema.org/a/301838.svg)](https://asciinema.org/a/301838)
+
+Run it with `cargo run --example dashboard` and see what else it can do by checking out `cargo run --example dashboard -- --help`.
+*/
+#[cfg(feature = "atty")]
+pub use atty;
+
+#[cfg(feature = "progress-tree")]
+///
+pub mod tree;
+
+///
+pub mod render;
+
+#[cfg(feature = "progress-tree-log")]
+pub use log::info;
+#[cfg(feature = "progress-tree-log")]
+pub use log::warn;
+
+#[cfg(any(feature = "humantime", feature = "time"))]
+///
+pub mod time;
+
+///
+pub mod unit;
+#[doc(inline)]
+pub use unit::Unit;
+
+///
+pub mod messages;
+///
+pub mod progress;
+
+mod traits;
+pub use traits::{Progress, Root, WeakRoot};
+
+mod throughput;
+pub use crate::throughput::Throughput;
+
+#[cfg(not(feature = "progress-tree-log"))]
+mod log {
+ /// Stub
+ #[macro_export(local_inner_macros)]
+ macro_rules! warn {
+ (target: $target:expr, $($arg:tt)+) => {};
+ ($($arg:tt)+) => {};
+ }
+ /// Stub
+ #[macro_export(local_inner_macros)]
+ macro_rules! info {
+ (target: $target:expr, $($arg:tt)+) => {};
+ ($($arg:tt)+) => {};
+ }
+}
diff --git a/vendor/prodash/src/messages.rs b/vendor/prodash/src/messages.rs
new file mode 100644
index 000000000..2abd63830
--- /dev/null
+++ b/vendor/prodash/src/messages.rs
@@ -0,0 +1,126 @@
+use std::time::SystemTime;
+
+/// The severity of a message
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
+pub enum MessageLevel {
+ /// Rarely sent information related to the progress, not to be confused with the progress itself
+ Info,
+ /// Used to indicate that a task has failed, along with the reason
+ Failure,
+ /// Indicates a task was completed successfully
+ Success,
+}
+
+/// A message to be stored along with the progress tree.
+///
+/// It is created by [`Tree::message(…)`](./struct.Item.html#method.message).
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct Message {
+ /// The time at which the message was sent.
+ pub time: SystemTime,
+ /// The severity of the message
+ pub level: MessageLevel,
+ /// The name of the task that created the `Message`
+ pub origin: String,
+ /// The message itself
+ pub message: String,
+}
+
+/// A ring buffer for messages.
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct MessageRingBuffer {
+ pub(crate) buf: Vec<Message>,
+ cursor: usize,
+ total: usize,
+}
+
+impl MessageRingBuffer {
+ /// Create a new instance the ability to hold `capacity` amount of messages.
+ pub fn with_capacity(capacity: usize) -> MessageRingBuffer {
+ MessageRingBuffer {
+ buf: Vec::with_capacity(capacity),
+ cursor: 0,
+ total: 0,
+ }
+ }
+
+ /// Push a `message` from `origin` at severity `level` into the buffer, possibly overwriting the last message added.
+ pub fn push_overwrite(&mut self, level: MessageLevel, origin: String, message: impl Into<String>) {
+ let msg = Message {
+ time: SystemTime::now(),
+ level,
+ origin,
+ message: message.into(),
+ };
+ if self.has_capacity() {
+ self.buf.push(msg)
+ } else {
+ self.buf[self.cursor] = msg;
+ self.cursor = (self.cursor + 1) % self.buf.len();
+ }
+ self.total = self.total.wrapping_add(1);
+ }
+
+ /// Copy all messages currently contained in the buffer to `out`.
+ pub fn copy_all(&self, out: &mut Vec<Message>) {
+ out.clear();
+ if self.buf.is_empty() {
+ return;
+ }
+ out.extend_from_slice(&self.buf[self.cursor % self.buf.len()..]);
+ if self.cursor != self.buf.len() {
+ out.extend_from_slice(&self.buf[..self.cursor]);
+ }
+ }
+
+ /// Copy all new messages into `out` that where received since the last time this method was called provided
+ /// its `previous` return value.
+ pub fn copy_new(&self, out: &mut Vec<Message>, previous: Option<MessageCopyState>) -> MessageCopyState {
+ out.clear();
+ match previous {
+ Some(MessageCopyState { cursor, buf_len, total }) => {
+ if self.total.saturating_sub(total) >= self.buf.capacity() {
+ self.copy_all(out);
+ } else {
+ let new_elements_below_cap = self.buf.len().saturating_sub(buf_len);
+ let cursor_ofs: isize = self.cursor as isize - cursor as isize;
+ match cursor_ofs {
+ // there was some capacity left without wrapping around
+ c if c == 0 => {
+ out.extend_from_slice(&self.buf[self.buf.len() - new_elements_below_cap..]);
+ }
+ // cursor advanced
+ c if c > 0 => {
+ out.extend_from_slice(&self.buf[(cursor % self.buf.len())..self.cursor]);
+ }
+ // cursor wrapped around
+ c if c < 0 => {
+ out.extend_from_slice(&self.buf[(cursor % self.buf.len())..]);
+ out.extend_from_slice(&self.buf[..self.cursor]);
+ }
+ _ => unreachable!("logic dictates that… yeah, you really shouldn't ever see this!"),
+ }
+ }
+ }
+ None => self.copy_all(out),
+ };
+ MessageCopyState {
+ cursor: self.cursor,
+ buf_len: self.buf.len(),
+ total: self.total,
+ }
+ }
+
+ fn has_capacity(&self) -> bool {
+ self.buf.len() < self.buf.capacity()
+ }
+}
+
+/// State used to keep track of what's new since the last time message were copied.
+///
+/// Note that due to the nature of a ring buffer, there is no guarantee that you see all messages.
+pub struct MessageCopyState {
+ cursor: usize,
+ buf_len: usize,
+ total: usize,
+}
diff --git a/vendor/prodash/src/progress/key.rs b/vendor/prodash/src/progress/key.rs
new file mode 100644
index 000000000..dae714d68
--- /dev/null
+++ b/vendor/prodash/src/progress/key.rs
@@ -0,0 +1,261 @@
+use std::ops::{Index, IndexMut};
+
+use crate::progress::Task;
+
+/// a level in the hierarchy of key components
+///
+/// _NOTE:_ This means we will show weird behaviour if there are more than 2^16 tasks at the same time on a level
+/// as multiple progress handles will manipulate the same state.
+pub type Level = u8;
+
+pub(crate) type Id = u16;
+
+/// A type identifying a spot in the hierarchy of `Tree` items.
+#[derive(Copy, Clone, Default, Hash, Eq, PartialEq, Ord, PartialOrd, Debug)]
+pub struct Key(Option<Id>, Option<Id>, Option<Id>, Option<Id>, Option<Id>, Option<Id>);
+
+/// Determines if a sibling is above or below in the given level of hierarchy
+#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
+#[allow(missing_docs)]
+pub enum SiblingLocation {
+ Above,
+ Below,
+ AboveAndBelow,
+ NotFound,
+}
+
+impl SiblingLocation {
+ fn merge(&mut self, other: SiblingLocation) {
+ use SiblingLocation::*;
+ *self = match (*self, other) {
+ (any, NotFound) => any,
+ (NotFound, any) => any,
+ (Above, Below) => AboveAndBelow,
+ (Below, Above) => AboveAndBelow,
+ (AboveAndBelow, _) => AboveAndBelow,
+ (_, AboveAndBelow) => AboveAndBelow,
+ (Above, Above) => Above,
+ (Below, Below) => Below,
+ };
+ }
+}
+
+impl Default for SiblingLocation {
+ fn default() -> Self {
+ SiblingLocation::NotFound
+ }
+}
+
+/// A type providing information about what's above and below `Tree` items.
+#[derive(Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd, Debug)]
+pub struct Adjacency(
+ pub SiblingLocation,
+ pub SiblingLocation,
+ pub SiblingLocation,
+ pub SiblingLocation,
+ pub SiblingLocation,
+ pub SiblingLocation,
+);
+
+impl Adjacency {
+ /// Return the level at which this sibling is located in the hierarchy.
+ pub fn level(&self) -> Level {
+ use SiblingLocation::*;
+ match self {
+ Adjacency(NotFound, NotFound, NotFound, NotFound, NotFound, NotFound) => 0,
+ Adjacency(_a, NotFound, NotFound, NotFound, NotFound, NotFound) => 1,
+ Adjacency(_a, _b, NotFound, NotFound, NotFound, NotFound) => 2,
+ Adjacency(_a, _b, _c, NotFound, NotFound, NotFound) => 3,
+ Adjacency(_a, _b, _c, _d, NotFound, NotFound) => 4,
+ Adjacency(_a, _b, _c, _d, _e, NotFound) => 5,
+ Adjacency(_a, _b, _c, _d, _e, _f) => 6,
+ }
+ }
+ /// Get a reference to the sibling location at `level`.
+ pub fn get(&self, level: Level) -> Option<&SiblingLocation> {
+ Some(match level {
+ 1 => &self.0,
+ 2 => &self.1,
+ 3 => &self.2,
+ 4 => &self.3,
+ 5 => &self.4,
+ 6 => &self.5,
+ _ => return None,
+ })
+ }
+ /// Get a mutable reference to the sibling location at `level`.
+ pub fn get_mut(&mut self, level: Level) -> Option<&mut SiblingLocation> {
+ Some(match level {
+ 1 => &mut self.0,
+ 2 => &mut self.1,
+ 3 => &mut self.2,
+ 4 => &mut self.3,
+ 5 => &mut self.4,
+ 6 => &mut self.5,
+ _ => return None,
+ })
+ }
+}
+
+impl Index<Level> for Adjacency {
+ type Output = SiblingLocation;
+ fn index(&self, index: Level) -> &Self::Output {
+ self.get(index).expect("adjacency index in bound")
+ }
+}
+
+impl IndexMut<Level> for Adjacency {
+ fn index_mut(&mut self, index: Level) -> &mut Self::Output {
+ self.get_mut(index).expect("adjacency index in bound")
+ }
+}
+
+impl Key {
+ /// Return the key to the child identified by `child_id` located in a new nesting level below `self`.
+ pub fn add_child(self, child_id: Id) -> Key {
+ match self {
+ Key(None, None, None, None, None, None) => Key(Some(child_id), None, None, None, None, None),
+ Key(a, None, None, None, None, None) => Key(a, Some(child_id), None, None, None, None),
+ Key(a, b, None, None, None, None) => Key(a, b, Some(child_id), None, None, None),
+ Key(a, b, c, None, None, None) => Key(a, b, c, Some(child_id), None, None),
+ Key(a, b, c, d, None, None) => Key(a, b, c, d, Some(child_id), None),
+ Key(a, b, c, d, e, _f) => {
+ crate::warn!("Maximum nesting level reached. Adding tasks to current parent");
+ Key(a, b, c, d, e, Some(child_id))
+ }
+ }
+ }
+
+ /// The level of hierarchy a node is placed in, i.e. the amount of path components
+ pub fn level(&self) -> Level {
+ match self {
+ Key(None, None, None, None, None, None) => 0,
+ Key(Some(_), None, None, None, None, None) => 1,
+ Key(Some(_), Some(_), None, None, None, None) => 2,
+ Key(Some(_), Some(_), Some(_), None, None, None) => 3,
+ Key(Some(_), Some(_), Some(_), Some(_), None, None) => 4,
+ Key(Some(_), Some(_), Some(_), Some(_), Some(_), None) => 5,
+ Key(Some(_), Some(_), Some(_), Some(_), Some(_), Some(_)) => 6,
+ _ => unreachable!("This is a bug - Keys follow a certain pattern"),
+ }
+ }
+
+ /// Return the identifier for the item at `level`.
+ fn get(&self, level: Level) -> Option<&Id> {
+ match level {
+ 1 => self.0.as_ref(),
+ 2 => self.1.as_ref(),
+ 3 => self.2.as_ref(),
+ 4 => self.3.as_ref(),
+ 5 => self.4.as_ref(),
+ 6 => self.5.as_ref(),
+ _ => None,
+ }
+ }
+
+ /// Return true if the item identified by `other` shares the parent at `parent_level`.
+ pub fn shares_parent_with(&self, other: &Key, parent_level: Level) -> bool {
+ if parent_level < 1 {
+ return true;
+ }
+ for level in 1..=parent_level {
+ if let (Some(lhs), Some(rhs)) = (self.get(level), other.get(level)) {
+ if lhs != rhs {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+ true
+ }
+
+ /// Compute the adjacency map for the key in `sorted` at the given `index`.
+ ///
+ /// It's vital that the invariant of `sorted` to actually be sorted by key is upheld
+ /// for the result to be reliable.
+ pub fn adjacency(sorted: &[(Key, Task)], index: usize) -> Adjacency {
+ use SiblingLocation::*;
+ let key = &sorted[index].0;
+ let key_level = key.level();
+ let mut adjecency = Adjacency::default();
+ if key_level == 0 {
+ return adjecency;
+ }
+
+ fn search<'a>(
+ iter: impl Iterator<Item = &'a (Key, Task)>,
+ key: &Key,
+ key_level: Level,
+ current_level: Level,
+ _id_at_level: Id,
+ ) -> Option<usize> {
+ iter.map(|(k, _)| k)
+ .take_while(|other| key.shares_parent_with(other, current_level.saturating_sub(1)))
+ .enumerate()
+ .find(|(_idx, k)| {
+ if current_level == key_level {
+ k.level() == key_level || k.level() + 1 == key_level
+ } else {
+ k.level() == current_level
+ }
+ })
+ .map(|(idx, _)| idx)
+ }
+
+ let upward_iter = |from: usize, key: &Key, level: Level, id_at_level: Id| {
+ search(sorted[..from].iter().rev(), key, key_level, level, id_at_level)
+ };
+ let downward_iter = |from: usize, key: &Key, level: Level, id_at_level: Id| {
+ sorted
+ .get(from + 1..)
+ .and_then(|s| search(s.iter(), key, key_level, level, id_at_level))
+ };
+
+ {
+ let mut cursor = index;
+ for level in (1..=key_level).rev() {
+ if level == 1 {
+ adjecency[level].merge(Above); // the root or any other sibling on level one
+ continue;
+ }
+ if let Some(key_offset) = upward_iter(cursor, key, level, key[level]) {
+ cursor = index.saturating_sub(key_offset);
+ adjecency[level].merge(Above);
+ }
+ }
+ }
+ {
+ let mut cursor = index;
+ for level in (1..=key_level).rev() {
+ if let Some(key_offset) = downward_iter(cursor, key, level, key[level]) {
+ cursor = index + key_offset;
+ adjecency[level].merge(Below);
+ }
+ }
+ }
+ for level in 1..key_level {
+ if key_level == 1 && index + 1 == sorted.len() {
+ continue;
+ }
+ adjecency[level] = match adjecency[level] {
+ Above | Below | NotFound => NotFound,
+ AboveAndBelow => AboveAndBelow,
+ };
+ }
+ adjecency
+ }
+
+ /// The maximum amount of path components we can represent.
+ pub const fn max_level() -> Level {
+ 6
+ }
+}
+
+impl Index<Level> for Key {
+ type Output = Id;
+
+ fn index(&self, index: Level) -> &Self::Output {
+ self.get(index).expect("key index in bound")
+ }
+}
diff --git a/vendor/prodash/src/progress/log.rs b/vendor/prodash/src/progress/log.rs
new file mode 100644
index 000000000..3410a5dc2
--- /dev/null
+++ b/vendor/prodash/src/progress/log.rs
@@ -0,0 +1,151 @@
+use std::{
+ sync::{
+ atomic::{AtomicBool, Ordering},
+ Arc,
+ },
+ time::Duration,
+};
+
+use crate::{
+ messages::MessageLevel,
+ progress::{Id, Step, StepShared},
+ Progress, Unit,
+};
+
+/// A [`Progress`] implementation which displays progress as it happens without the use of a renderer.
+///
+/// Note that this incurs considerable performance cost as each progress calls ends up getting the system time
+/// to see if progress information should actually be emitted.
+pub struct Log {
+ name: String,
+ id: Id,
+ max: Option<usize>,
+ unit: Option<Unit>,
+ step: usize,
+ current_level: usize,
+ max_level: usize,
+ trigger: Arc<AtomicBool>,
+}
+
+const EMIT_LOG_EVERY_S: f32 = 0.5;
+const SEP: &str = "::";
+
+impl Log {
+ /// Create a new instance from `name` while displaying progress information only up to `max_level`.
+ pub fn new(name: impl Into<String>, max_level: Option<usize>) -> Self {
+ let trigger = Arc::new(AtomicBool::new(true));
+ std::thread::spawn({
+ let duration = Duration::from_secs_f32(EMIT_LOG_EVERY_S);
+ let trigger = Arc::downgrade(&trigger);
+ move || {
+ while let Some(t) = trigger.upgrade() {
+ t.store(true, Ordering::Relaxed);
+ std::thread::sleep(duration);
+ }
+ }
+ });
+ Log {
+ name: name.into(),
+ id: crate::progress::UNKNOWN,
+ current_level: 0,
+ max_level: max_level.unwrap_or(usize::MAX),
+ max: None,
+ step: 0,
+ unit: None,
+ trigger,
+ }
+ }
+}
+
+impl Progress for Log {
+ type SubProgress = Log;
+
+ fn add_child(&mut self, name: impl Into<String>) -> Self::SubProgress {
+ self.add_child_with_id(name, crate::progress::UNKNOWN)
+ }
+
+ fn add_child_with_id(&mut self, name: impl Into<String>, id: Id) -> Self::SubProgress {
+ Log {
+ name: format!("{}{}{}", self.name, SEP, Into::<String>::into(name)),
+ id,
+ current_level: self.current_level + 1,
+ max_level: self.max_level,
+ step: 0,
+ max: None,
+ unit: None,
+ trigger: Arc::clone(&self.trigger),
+ }
+ }
+
+ fn init(&mut self, max: Option<usize>, unit: Option<Unit>) {
+ self.max = max;
+ self.unit = unit;
+ }
+
+ fn set(&mut self, step: usize) {
+ self.step = step;
+ if self.current_level > self.max_level {
+ return;
+ }
+ if self.trigger.swap(false, Ordering::Relaxed) {
+ match (self.max, &self.unit) {
+ (max, Some(unit)) => log::info!("{} → {}", self.name, unit.display(step, max, None)),
+ (Some(max), None) => log::info!("{} → {} / {}", self.name, step, max),
+ (None, None) => log::info!("{} → {}", self.name, step),
+ }
+ }
+ }
+
+ fn unit(&self) -> Option<Unit> {
+ self.unit.clone()
+ }
+
+ fn max(&self) -> Option<usize> {
+ self.max
+ }
+
+ fn set_max(&mut self, max: Option<Step>) -> Option<Step> {
+ let prev = self.max;
+ self.max = max;
+ prev
+ }
+
+ fn step(&self) -> usize {
+ self.step
+ }
+
+ fn inc_by(&mut self, step: usize) {
+ self.set(self.step + step)
+ }
+
+ fn set_name(&mut self, name: impl Into<String>) {
+ let name = name.into();
+ self.name = self
+ .name
+ .split("::")
+ .next()
+ .map(|parent| format!("{}{}{}", parent.to_owned(), SEP, name))
+ .unwrap_or(name);
+ }
+
+ fn name(&self) -> Option<String> {
+ self.name.split(SEP).nth(1).map(ToOwned::to_owned)
+ }
+
+ fn id(&self) -> Id {
+ self.id
+ }
+
+ fn message(&mut self, level: MessageLevel, message: impl Into<String>) {
+ let message: String = message.into();
+ match level {
+ MessageLevel::Info => log::info!("ℹ{} → {}", self.name, message),
+ MessageLevel::Failure => log::error!("𐄂{} → {}", self.name, message),
+ MessageLevel::Success => log::info!("✓{} → {}", self.name, message),
+ }
+ }
+
+ fn counter(&self) -> Option<StepShared> {
+ None
+ }
+}
diff --git a/vendor/prodash/src/progress/mod.rs b/vendor/prodash/src/progress/mod.rs
new file mode 100644
index 000000000..0d9c694cf
--- /dev/null
+++ b/vendor/prodash/src/progress/mod.rs
@@ -0,0 +1,112 @@
+use std::{
+ sync::{
+ atomic::{AtomicUsize, Ordering},
+ Arc,
+ },
+ time::SystemTime,
+};
+
+use crate::unit::Unit;
+
+///
+pub mod key;
+#[doc(inline)]
+pub use key::Key;
+
+mod utils;
+
+#[cfg(feature = "progress-log")]
+mod log;
+pub use utils::{Discard, DoOrDiscard, Either, ThroughputOnDrop};
+
+#[cfg(feature = "progress-log")]
+pub use self::log::Log;
+
+/// Four bytes of function-local unique and stable identifier for each item added as progress,
+/// like b"TREE" or b"FILE".
+///
+/// Note that uniqueness only relates to one particular method call where those interested in its progress
+/// may assume certain stable ids to look for when selecting specific bits of progress to process.
+pub type Id = [u8; 4];
+
+/// The default Id to use if there is no need for an id.
+///
+/// This is the default unless applications wish to make themselves more introspectable.
+pub const UNKNOWN: Id = *b"\0\0\0\0";
+
+/// The amount of steps a progress can make
+pub type Step = usize;
+
+/// As step, but shareable.
+pub type StepShared = Arc<AtomicUsize>;
+
+/// Indicate whether a progress can or cannot be made.
+#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)]
+pub enum State {
+ /// Indicates a task is blocked and cannot indicate progress, optionally until the
+ /// given time. The task cannot easily be interrupted.
+ Blocked(&'static str, Option<SystemTime>),
+ /// Indicates a task cannot indicate progress, optionally until the
+ /// given time. The task can be interrupted.
+ Halted(&'static str, Option<SystemTime>),
+ /// The task is running
+ Running,
+}
+
+impl Default for State {
+ fn default() -> Self {
+ State::Running
+ }
+}
+
+/// Progress associated with some item in the progress tree.
+#[derive(Clone, Default, Debug)]
+pub struct Value {
+ /// The amount of progress currently made
+ pub step: StepShared,
+ /// The step at which no further progress has to be made.
+ ///
+ /// If unset, the progress is unbounded.
+ pub done_at: Option<Step>,
+ /// The unit associated with the progress.
+ pub unit: Option<Unit>,
+ /// Whether progress can be made or not
+ pub state: State,
+}
+
+impl std::hash::Hash for Value {
+ fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+ let Self {
+ step,
+ done_at,
+ unit,
+ state: our_state,
+ } = self;
+ done_at.hash(state);
+ unit.hash(state);
+ our_state.hash(state);
+ step.load(Ordering::Relaxed).hash(state);
+ }
+}
+
+impl Value {
+ /// Returns a number between `Some(0.0)` and `Some(1.0)`, or `None` if the progress is unbounded.
+ ///
+ /// A task half done would return `Some(0.5)`.
+ pub fn fraction(&self) -> Option<f32> {
+ self.done_at
+ .map(|done_at| self.step.load(Ordering::SeqCst) as f32 / done_at as f32)
+ }
+}
+
+/// The value associated with a spot in the hierarchy.
+#[derive(Clone, Default, Debug, Hash)]
+pub struct Task {
+ /// The name of the `Item` or task.
+ pub name: String,
+ /// The stable identifier of this task.
+ /// Useful for selecting specific tasks out of a set of them.
+ pub id: Id,
+ /// The progress itself, unless this value belongs to an `Item` serving as organizational unit.
+ pub progress: Option<Value>,
+}
diff --git a/vendor/prodash/src/progress/utils.rs b/vendor/prodash/src/progress/utils.rs
new file mode 100644
index 000000000..549a0b6e5
--- /dev/null
+++ b/vendor/prodash/src/progress/utils.rs
@@ -0,0 +1,337 @@
+use crate::{messages::MessageLevel, progress::Id, Progress, Unit};
+
+/// An implementation of [`Progress`] which discards all calls.
+pub struct Discard;
+
+impl Progress for Discard {
+ type SubProgress = Discard;
+
+ fn add_child(&mut self, _name: impl Into<String>) -> Self::SubProgress {
+ Discard
+ }
+
+ fn add_child_with_id(&mut self, _name: impl Into<String>, _id: Id) -> Self::SubProgress {
+ Discard
+ }
+
+ fn init(&mut self, _max: Option<usize>, _unit: Option<Unit>) {}
+
+ fn set(&mut self, _step: usize) {}
+
+ fn set_max(&mut self, _max: Option<Step>) -> Option<Step> {
+ None
+ }
+
+ fn step(&self) -> usize {
+ 0
+ }
+
+ fn inc_by(&mut self, _step: usize) {}
+
+ fn set_name(&mut self, _name: impl Into<String>) {}
+
+ fn name(&self) -> Option<String> {
+ None
+ }
+
+ fn id(&self) -> Id {
+ crate::progress::UNKNOWN
+ }
+
+ fn message(&mut self, _level: MessageLevel, _message: impl Into<String>) {}
+
+ fn counter(&self) -> Option<StepShared> {
+ None
+ }
+}
+
+/// An implementation of [`Progress`] showing either one or the other implementation.
+///
+/// Useful in conjunction with [`Discard`] and a working implementation, making it as a form of `Option<Progress>` which
+/// can be passed to methods requiring `impl Progress`.
+/// See [`DoOrDiscard`] for an incarnation of this.
+#[allow(missing_docs)]
+pub enum Either<L, R> {
+ Left(L),
+ Right(R),
+}
+
+impl<L, R> Progress for Either<L, R>
+where
+ L: Progress,
+ R: Progress,
+{
+ type SubProgress = Either<L::SubProgress, R::SubProgress>;
+
+ fn add_child(&mut self, name: impl Into<String>) -> Self::SubProgress {
+ match self {
+ Either::Left(l) => Either::Left(l.add_child(name)),
+ Either::Right(r) => Either::Right(r.add_child(name)),
+ }
+ }
+
+ fn add_child_with_id(&mut self, name: impl Into<String>, id: Id) -> Self::SubProgress {
+ match self {
+ Either::Left(l) => Either::Left(l.add_child_with_id(name, id)),
+ Either::Right(r) => Either::Right(r.add_child_with_id(name, id)),
+ }
+ }
+
+ fn init(&mut self, max: Option<usize>, unit: Option<Unit>) {
+ match self {
+ Either::Left(l) => l.init(max, unit),
+ Either::Right(r) => r.init(max, unit),
+ }
+ }
+
+ fn set(&mut self, step: usize) {
+ match self {
+ Either::Left(l) => l.set(step),
+ Either::Right(r) => r.set(step),
+ }
+ }
+
+ fn unit(&self) -> Option<Unit> {
+ match self {
+ Either::Left(l) => l.unit(),
+ Either::Right(r) => r.unit(),
+ }
+ }
+
+ fn max(&self) -> Option<usize> {
+ match self {
+ Either::Left(l) => l.max(),
+ Either::Right(r) => r.max(),
+ }
+ }
+
+ fn set_max(&mut self, max: Option<Step>) -> Option<Step> {
+ match self {
+ Either::Left(l) => l.set_max(max),
+ Either::Right(r) => r.set_max(max),
+ }
+ }
+
+ fn step(&self) -> usize {
+ match self {
+ Either::Left(l) => l.step(),
+ Either::Right(r) => r.step(),
+ }
+ }
+
+ fn inc_by(&mut self, step: usize) {
+ match self {
+ Either::Left(l) => l.inc_by(step),
+ Either::Right(r) => r.inc_by(step),
+ }
+ }
+
+ fn set_name(&mut self, name: impl Into<String>) {
+ match self {
+ Either::Left(l) => l.set_name(name),
+ Either::Right(r) => r.set_name(name),
+ }
+ }
+
+ fn name(&self) -> Option<String> {
+ match self {
+ Either::Left(l) => l.name(),
+ Either::Right(r) => r.name(),
+ }
+ }
+
+ fn id(&self) -> Id {
+ todo!()
+ }
+
+ fn message(&mut self, level: MessageLevel, message: impl Into<String>) {
+ match self {
+ Either::Left(l) => l.message(level, message),
+ Either::Right(r) => r.message(level, message),
+ }
+ }
+
+ fn counter(&self) -> Option<StepShared> {
+ match self {
+ Either::Left(l) => l.counter(),
+ Either::Right(r) => r.counter(),
+ }
+ }
+}
+
+/// An implementation of `Progress` which can be created easily from `Option<impl Progress>`.
+pub struct DoOrDiscard<T>(Either<T, Discard>);
+
+impl<T> From<Option<T>> for DoOrDiscard<T>
+where
+ T: Progress,
+{
+ fn from(p: Option<T>) -> Self {
+ match p {
+ Some(p) => DoOrDiscard(Either::Left(p)),
+ None => DoOrDiscard(Either::Right(Discard)),
+ }
+ }
+}
+
+impl<T: Progress> DoOrDiscard<T> {
+ /// Obtain either the original [`Progress`] implementation or `None`.
+ pub fn into_inner(self) -> Option<T> {
+ match self {
+ DoOrDiscard(Either::Left(p)) => Some(p),
+ DoOrDiscard(Either::Right(_)) => None,
+ }
+ }
+
+ /// Take out the implementation of [`Progress`] and replace it with [`Discard`].
+ pub fn take(&mut self) -> Option<T> {
+ let this = std::mem::replace(self, DoOrDiscard::from(None));
+ match this {
+ DoOrDiscard(Either::Left(p)) => Some(p),
+ DoOrDiscard(Either::Right(_)) => None,
+ }
+ }
+}
+
+impl<T> Progress for DoOrDiscard<T>
+where
+ T: Progress,
+{
+ type SubProgress = DoOrDiscard<T::SubProgress>;
+
+ fn add_child(&mut self, name: impl Into<String>) -> Self::SubProgress {
+ DoOrDiscard(self.0.add_child(name))
+ }
+
+ fn add_child_with_id(&mut self, name: impl Into<String>, id: Id) -> Self::SubProgress {
+ DoOrDiscard(self.0.add_child_with_id(name, id))
+ }
+
+ fn init(&mut self, max: Option<usize>, unit: Option<Unit>) {
+ self.0.init(max, unit)
+ }
+
+ fn set(&mut self, step: usize) {
+ self.0.set(step)
+ }
+
+ fn unit(&self) -> Option<Unit> {
+ self.0.unit()
+ }
+
+ fn max(&self) -> Option<usize> {
+ self.0.max()
+ }
+
+ fn set_max(&mut self, max: Option<Step>) -> Option<Step> {
+ self.0.set_max(max)
+ }
+
+ fn step(&self) -> usize {
+ self.0.step()
+ }
+
+ fn inc_by(&mut self, step: usize) {
+ self.0.inc_by(step)
+ }
+
+ fn set_name(&mut self, name: impl Into<String>) {
+ self.0.set_name(name);
+ }
+
+ fn name(&self) -> Option<String> {
+ self.0.name()
+ }
+
+ fn id(&self) -> Id {
+ self.0.id()
+ }
+
+ fn message(&mut self, level: MessageLevel, message: impl Into<String>) {
+ self.0.message(level, message)
+ }
+
+ fn counter(&self) -> Option<StepShared> {
+ self.0.counter()
+ }
+}
+
+use std::time::Instant;
+
+use crate::progress::{Step, StepShared};
+
+/// Emit a message with throughput information when the instance is dropped.
+pub struct ThroughputOnDrop<T: Progress>(T, Instant);
+
+impl<T: Progress> ThroughputOnDrop<T> {
+ /// Create a new instance by providing the `inner` [`Progress`] implementation.
+ pub fn new(inner: T) -> Self {
+ ThroughputOnDrop(inner, Instant::now())
+ }
+}
+
+impl<T: Progress> Progress for ThroughputOnDrop<T> {
+ type SubProgress = T::SubProgress;
+
+ fn add_child(&mut self, name: impl Into<String>) -> Self::SubProgress {
+ self.0.add_child(name)
+ }
+
+ fn add_child_with_id(&mut self, name: impl Into<String>, id: Id) -> Self::SubProgress {
+ self.0.add_child_with_id(name, id)
+ }
+
+ fn init(&mut self, max: Option<usize>, unit: Option<Unit>) {
+ self.0.init(max, unit)
+ }
+
+ fn set(&mut self, step: usize) {
+ self.0.set(step)
+ }
+
+ fn unit(&self) -> Option<Unit> {
+ self.0.unit()
+ }
+
+ fn max(&self) -> Option<usize> {
+ self.0.max()
+ }
+
+ fn set_max(&mut self, max: Option<Step>) -> Option<Step> {
+ self.0.set_max(max)
+ }
+
+ fn step(&self) -> usize {
+ self.0.step()
+ }
+
+ fn inc_by(&mut self, step: usize) {
+ self.0.inc_by(step)
+ }
+
+ fn set_name(&mut self, name: impl Into<String>) {
+ self.0.set_name(name)
+ }
+
+ fn name(&self) -> Option<String> {
+ self.0.name()
+ }
+
+ fn id(&self) -> Id {
+ self.0.id()
+ }
+
+ fn message(&mut self, level: MessageLevel, message: impl Into<String>) {
+ self.0.message(level, message)
+ }
+
+ fn counter(&self) -> Option<StepShared> {
+ self.0.counter()
+ }
+}
+
+impl<T: Progress> Drop for ThroughputOnDrop<T> {
+ fn drop(&mut self) {
+ self.0.show_throughput(self.1)
+ }
+}
diff --git a/vendor/prodash/src/render/line/draw.rs b/vendor/prodash/src/render/line/draw.rs
new file mode 100644
index 000000000..cfd5eaae3
--- /dev/null
+++ b/vendor/prodash/src/render/line/draw.rs
@@ -0,0 +1,350 @@
+use std::{
+ collections::{hash_map::DefaultHasher, VecDeque},
+ hash::{Hash, Hasher},
+ io,
+ ops::RangeInclusive,
+ sync::atomic::Ordering,
+};
+
+use crosstermion::{
+ ansi_term::{ANSIString, ANSIStrings, Color, Style},
+ color,
+};
+use unicode_width::UnicodeWidthStr;
+
+use crate::{
+ messages::{Message, MessageCopyState, MessageLevel},
+ progress::{self, Value},
+ unit, Root, Throughput,
+};
+
+#[derive(Default)]
+pub struct State {
+ tree: Vec<(progress::Key, progress::Task)>,
+ messages: Vec<Message>,
+ for_next_copy: Option<MessageCopyState>,
+ /// The size of the message origin, tracking the terminal height so things potentially off screen don't influence width anymore.
+ message_origin_size: VecDeque<usize>,
+ /// The maximum progress midpoint (point till progress bar starts) seen at the last tick
+ last_progress_midpoint: Option<u16>,
+ /// The amount of blocks per line we have written last time.
+ blocks_per_line: VecDeque<u16>,
+ pub throughput: Option<Throughput>,
+}
+
+impl State {
+ pub(crate) fn update_from_progress(&mut self, progress: &impl Root) -> bool {
+ let mut hasher = DefaultHasher::new();
+ self.tree.hash(&mut hasher);
+ let prev_hash = hasher.finish();
+
+ progress.sorted_snapshot(&mut self.tree);
+ let mut hasher = DefaultHasher::new();
+ self.tree.hash(&mut hasher);
+ let cur_hash = hasher.finish();
+
+ self.for_next_copy = progress
+ .copy_new_messages(&mut self.messages, self.for_next_copy.take())
+ .into();
+ prev_hash != cur_hash
+ }
+ pub(crate) fn clear(&mut self) {
+ self.tree.clear();
+ self.messages.clear();
+ self.for_next_copy.take();
+ }
+}
+
+pub struct Options {
+ pub level_filter: Option<RangeInclusive<progress::key::Level>>,
+ pub terminal_dimensions: (u16, u16),
+ pub keep_running_if_progress_is_empty: bool,
+ pub output_is_terminal: bool,
+ pub colored: bool,
+ pub timestamp: bool,
+ pub hide_cursor: bool,
+}
+
+fn messages(
+ out: &mut impl io::Write,
+ state: &mut State,
+ colored: bool,
+ max_height: usize,
+ timestamp: bool,
+) -> io::Result<()> {
+ let mut brush = color::Brush::new(colored);
+ fn to_color(level: MessageLevel) -> Color {
+ use crate::messages::MessageLevel::*;
+ match level {
+ Info => Color::White,
+ Success => Color::Green,
+ Failure => Color::Red,
+ }
+ }
+ let mut tokens: Vec<ANSIString<'_>> = Vec::with_capacity(6);
+ let mut current_maximum = state.message_origin_size.iter().max().cloned().unwrap_or(0);
+ for Message {
+ time,
+ level,
+ origin,
+ message,
+ } in &state.messages
+ {
+ tokens.clear();
+ let blocks_drawn_during_previous_tick = state.blocks_per_line.pop_front().unwrap_or(0);
+ let message_block_len = origin.width();
+ current_maximum = current_maximum.max(message_block_len);
+ if state.message_origin_size.len() == max_height {
+ state.message_origin_size.pop_front();
+ }
+ state.message_origin_size.push_back(message_block_len);
+
+ let color = to_color(*level);
+ tokens.push(" ".into());
+ if timestamp {
+ tokens.push(
+ brush
+ .style(color.dimmed().on(Color::Yellow))
+ .paint(crate::time::format_time_for_messages(*time)),
+ );
+ tokens.push(Style::default().paint(" "));
+ } else {
+ tokens.push("".into());
+ };
+ tokens.push(brush.style(Style::default().dimmed()).paint(format!(
+ "{:>fill_size$}{}",
+ "",
+ origin,
+ fill_size = current_maximum - message_block_len,
+ )));
+ tokens.push(" ".into());
+ tokens.push(brush.style(color.bold()).paint(message));
+ let message_block_count = block_count_sans_ansi_codes(&tokens);
+ write!(out, "{}", ANSIStrings(tokens.as_slice()))?;
+
+ if blocks_drawn_during_previous_tick > message_block_count {
+ newline_with_overdraw(out, &tokens, blocks_drawn_during_previous_tick)?;
+ } else {
+ writeln!(out)?;
+ }
+ }
+ Ok(())
+}
+
+pub fn all(out: &mut impl io::Write, show_progress: bool, state: &mut State, config: &Options) -> io::Result<()> {
+ if !config.keep_running_if_progress_is_empty && state.tree.is_empty() {
+ return Err(io::Error::new(io::ErrorKind::Other, "stop as progress is empty"));
+ }
+ messages(
+ out,
+ state,
+ config.colored,
+ config.terminal_dimensions.1 as usize,
+ config.timestamp,
+ )?;
+
+ if show_progress && config.output_is_terminal {
+ if let Some(tp) = state.throughput.as_mut() {
+ tp.update_elapsed();
+ }
+ let level_range = config
+ .level_filter
+ .clone()
+ .unwrap_or(RangeInclusive::new(0, progress::key::Level::max_value()));
+ let lines_to_be_drawn = state
+ .tree
+ .iter()
+ .filter(|(k, _)| level_range.contains(&k.level()))
+ .count();
+ if state.blocks_per_line.len() < lines_to_be_drawn {
+ state.blocks_per_line.resize(lines_to_be_drawn, 0);
+ }
+ let mut tokens: Vec<ANSIString<'_>> = Vec::with_capacity(4);
+ let mut max_midpoint = 0;
+ for ((key, value), ref mut blocks_in_last_iteration) in state
+ .tree
+ .iter()
+ .filter(|(k, _)| level_range.contains(&k.level()))
+ .zip(state.blocks_per_line.iter_mut())
+ {
+ max_midpoint = max_midpoint.max(
+ format_progress(
+ key,
+ value,
+ config.terminal_dimensions.0,
+ config.colored,
+ state.last_progress_midpoint,
+ state
+ .throughput
+ .as_mut()
+ .and_then(|tp| tp.update_and_get(key, value.progress.as_ref())),
+ &mut tokens,
+ )
+ .unwrap_or(0),
+ );
+ write!(out, "{}", ANSIStrings(tokens.as_slice()))?;
+
+ **blocks_in_last_iteration = newline_with_overdraw(out, &tokens, **blocks_in_last_iteration)?;
+ }
+ if let Some(tp) = state.throughput.as_mut() {
+ tp.reconcile(&state.tree);
+ }
+ state.last_progress_midpoint = Some(max_midpoint);
+ // overwrite remaining lines that we didn't touch naturally
+ let lines_drawn = lines_to_be_drawn;
+ if state.blocks_per_line.len() > lines_drawn {
+ for blocks_in_last_iteration in state.blocks_per_line.iter().skip(lines_drawn) {
+ writeln!(out, "{:>width$}", "", width = *blocks_in_last_iteration as usize)?;
+ }
+ // Move cursor back to end of the portion we have actually drawn
+ crosstermion::execute!(out, crosstermion::cursor::MoveUp(state.blocks_per_line.len() as u16))?;
+ state.blocks_per_line.resize(lines_drawn, 0);
+ } else if lines_drawn > 0 {
+ crosstermion::execute!(out, crosstermion::cursor::MoveUp(lines_drawn as u16))?;
+ }
+ }
+ Ok(())
+}
+
+/// Must be called directly after `tokens` were drawn, without newline. Takes care of adding the newline.
+fn newline_with_overdraw(
+ out: &mut impl io::Write,
+ tokens: &[ANSIString<'_>],
+ blocks_in_last_iteration: u16,
+) -> io::Result<u16> {
+ let current_block_count = block_count_sans_ansi_codes(tokens);
+ if blocks_in_last_iteration > current_block_count {
+ // fill to the end of line to overwrite what was previously there
+ writeln!(
+ out,
+ "{:>width$}",
+ "",
+ width = (blocks_in_last_iteration - current_block_count) as usize
+ )?;
+ } else {
+ writeln!(out)?;
+ };
+ Ok(current_block_count)
+}
+
+fn block_count_sans_ansi_codes(strings: &[ANSIString<'_>]) -> u16 {
+ strings.iter().map(|s| s.width() as u16).sum()
+}
+
+fn draw_progress_bar<'a>(
+ p: &Value,
+ style: Style,
+ mut blocks_available: u16,
+ colored: bool,
+ buf: &mut Vec<ANSIString<'a>>,
+) {
+ let mut brush = color::Brush::new(colored);
+ let styled_brush = brush.style(style);
+
+ blocks_available = blocks_available.saturating_sub(3); // account for…I don't really know it's magic
+ buf.push(" [".into());
+ match p.fraction() {
+ Some(mut fraction) => {
+ fraction = fraction.min(1.0);
+ blocks_available = blocks_available.saturating_sub(1); // account for '>' apparently
+ let progress_blocks = (blocks_available as f32 * fraction).floor() as usize;
+ buf.push(styled_brush.paint(format!("{:=<width$}", "", width = progress_blocks)));
+ buf.push(styled_brush.paint(">"));
+ buf.push(styled_brush.style(style.dimmed()).paint(format!(
+ "{:-<width$}",
+ "",
+ width = (blocks_available - progress_blocks as u16) as usize
+ )));
+ }
+ None => {
+ const CHARS: [char; 6] = ['=', '=', '=', ' ', ' ', ' '];
+ buf.push(
+ styled_brush.paint(
+ (p.step.load(Ordering::SeqCst)..std::usize::MAX)
+ .take(blocks_available as usize)
+ .map(|idx| CHARS[idx % CHARS.len()])
+ .rev()
+ .collect::<String>(),
+ ),
+ );
+ }
+ }
+ buf.push("]".into());
+}
+
+fn progress_style(p: &Value) -> Style {
+ use crate::progress::State::*;
+ match p.state {
+ Running => if let Some(fraction) = p.fraction() {
+ if fraction > 0.8 {
+ Color::Green
+ } else {
+ Color::Yellow
+ }
+ } else {
+ Color::White
+ }
+ .normal(),
+ Halted(_, _) => Color::Red.dimmed(),
+ Blocked(_, _) => Color::Red.normal(),
+ }
+}
+
+fn format_progress<'a>(
+ key: &progress::Key,
+ value: &'a progress::Task,
+ column_count: u16,
+ colored: bool,
+ midpoint: Option<u16>,
+ throughput: Option<unit::display::Throughput>,
+ buf: &mut Vec<ANSIString<'a>>,
+) -> Option<u16> {
+ let mut brush = color::Brush::new(colored);
+ buf.clear();
+
+ buf.push(Style::new().paint(format!("{:>level$}", "", level = key.level() as usize)));
+ match value.progress.as_ref() {
+ Some(progress) => {
+ let style = progress_style(progress);
+ buf.push(brush.style(Color::Cyan.bold()).paint(&value.name));
+ buf.push(" ".into());
+
+ let pre_unit = buf.len();
+ let values_brush = brush.style(Style::new().bold().dimmed());
+ match progress.unit.as_ref() {
+ Some(unit) => {
+ let mut display = unit.display(progress.step.load(Ordering::SeqCst), progress.done_at, throughput);
+ buf.push(values_brush.paint(display.values().to_string()));
+ buf.push(" ".into());
+ buf.push(display.unit().to_string().into());
+ }
+ None => {
+ buf.push(values_brush.paint(match progress.done_at {
+ Some(done_at) => format!("{}/{}", progress.step.load(Ordering::SeqCst), done_at),
+ None => format!("{}", progress.step.load(Ordering::SeqCst)),
+ }));
+ }
+ }
+ let desired_midpoint = block_count_sans_ansi_codes(buf.as_slice());
+ let actual_midpoint = if let Some(midpoint) = midpoint {
+ let padding = midpoint.saturating_sub(desired_midpoint);
+ if padding > 0 {
+ buf.insert(pre_unit, " ".repeat(padding as usize).into());
+ }
+ block_count_sans_ansi_codes(buf.as_slice())
+ } else {
+ desired_midpoint
+ };
+ let blocks_left = column_count.saturating_sub(actual_midpoint);
+ if blocks_left > 0 {
+ draw_progress_bar(progress, style, blocks_left, colored, buf);
+ }
+ Some(desired_midpoint)
+ }
+ None => {
+ // headline only - FIXME: would have to truncate it if it is too long for the line…
+ buf.push(brush.style(Color::White.bold()).paint(&value.name));
+ None
+ }
+ }
+}
diff --git a/vendor/prodash/src/render/line/engine.rs b/vendor/prodash/src/render/line/engine.rs
new file mode 100644
index 000000000..d440fb159
--- /dev/null
+++ b/vendor/prodash/src/render/line/engine.rs
@@ -0,0 +1,341 @@
+#[cfg(feature = "signal-hook")]
+use std::sync::Arc;
+use std::{
+ io,
+ ops::RangeInclusive,
+ sync::atomic::{AtomicBool, Ordering},
+ time::Duration,
+};
+
+use crate::{progress, render::line::draw, Throughput, WeakRoot};
+
+/// Options used for configuring a [line renderer][render()].
+#[derive(Clone)]
+pub struct Options {
+ /// If true, _(default true)_, we assume the output stream belongs to a terminal.
+ ///
+ /// If false, we won't print any live progress, only log messages.
+ pub output_is_terminal: bool,
+
+ /// If true, _(default: true)_ we will display color. You should use `output_is_terminal && crosstermion::should_colorize()`
+ /// to determine this value.
+ ///
+ /// Please note that you can enforce color even if the output stream is not connected to a terminal by setting
+ /// this field to true.
+ pub colored: bool,
+
+ /// If true, _(default: false)_, a timestamp will be shown before each message.
+ pub timestamp: bool,
+
+ /// The amount of columns and rows to use for drawing. Defaults to (80, 20).
+ pub terminal_dimensions: (u16, u16),
+
+ /// If true, _(default: false)_, the cursor will be hidden for a more visually appealing display.
+ ///
+ /// Please note that you must make sure the line renderer is properly shut down to restore the previous cursor
+ /// settings. See the `signal-hook` documentation in the README for more information.
+ pub hide_cursor: bool,
+
+ /// If true, (default false), we will keep track of the previous progress state to derive
+ /// continuous throughput information from. Throughput will only show for units which have
+ /// explicitly enabled it, it is opt-in.
+ ///
+ /// This comes at the cost of additional memory and CPU time.
+ pub throughput: bool,
+
+ /// If set, specify all levels that should be shown. Otherwise all available levels are shown.
+ ///
+ /// This is useful to filter out high-noise lower level progress items in the tree.
+ pub level_filter: Option<RangeInclusive<progress::key::Level>>,
+
+ /// If set, progress will only actually be shown after the given duration. Log messages will always be shown without delay.
+ ///
+ /// This option can be useful to not enforce progress for short actions, causing it to flicker.
+ /// Please note that this won't affect display of messages, which are simply logged.
+ pub initial_delay: Option<Duration>,
+
+ /// The amount of frames to draw per second. If below 1.0, it determines the amount of seconds between the frame.
+ ///
+ /// *e.g.* 1.0/4.0 is one frame every 4 seconds.
+ pub frames_per_second: f32,
+
+ /// If true (default: true), we will keep waiting for progress even after we encountered an empty list of drawable progress items.
+ ///
+ /// Please note that you should add at least one item to the `prodash::Tree` before launching the application or else
+ /// risk a race causing nothing to be rendered at all.
+ pub keep_running_if_progress_is_empty: bool,
+}
+
+/// The kind of stream to use for auto-configuration.
+pub enum StreamKind {
+ /// Standard output
+ Stdout,
+ /// Standard error
+ Stderr,
+}
+
+#[cfg(feature = "render-line-autoconfigure")]
+impl From<StreamKind> for atty::Stream {
+ fn from(s: StreamKind) -> Self {
+ match s {
+ StreamKind::Stdout => atty::Stream::Stdout,
+ StreamKind::Stderr => atty::Stream::Stderr,
+ }
+ }
+}
+
+/// Convenience
+impl Options {
+ /// Automatically configure (and overwrite) the following fields based on terminal configuration.
+ ///
+ /// * output_is_terminal
+ /// * colored
+ /// * terminal_dimensions
+ /// * hide-cursor (based on presence of 'signal-hook' feature.
+ #[cfg(feature = "render-line-autoconfigure")]
+ pub fn auto_configure(mut self, output: StreamKind) -> Self {
+ self.output_is_terminal = atty::is(output.into());
+ self.colored = self.output_is_terminal && crosstermion::color::allowed();
+ self.terminal_dimensions = crosstermion::terminal::size().unwrap_or((80, 20));
+ #[cfg(feature = "signal-hook")]
+ self.auto_hide_cursor();
+ self
+ }
+ #[cfg(all(feature = "render-line-autoconfigure", feature = "signal-hook"))]
+ fn auto_hide_cursor(&mut self) {
+ self.hide_cursor = true;
+ }
+ #[cfg(not(feature = "render-line-autoconfigure"))]
+ /// No-op - only available with the `render-line-autoconfigure` feature toggle.
+ pub fn auto_configure(self, _output: StreamKind) -> Self {
+ self
+ }
+}
+
+impl Default for Options {
+ fn default() -> Self {
+ Options {
+ output_is_terminal: true,
+ colored: true,
+ timestamp: false,
+ terminal_dimensions: (80, 20),
+ hide_cursor: false,
+ level_filter: None,
+ initial_delay: None,
+ frames_per_second: 6.0,
+ throughput: false,
+ keep_running_if_progress_is_empty: true,
+ }
+ }
+}
+
+/// A handle to the render thread, which when dropped will instruct it to stop showing progress.
+pub struct JoinHandle {
+ inner: Option<std::thread::JoinHandle<io::Result<()>>>,
+ connection: std::sync::mpsc::SyncSender<Event>,
+ // If we disconnect before sending a Quit event, the selector continuously informs about the 'Disconnect' state
+ disconnected: bool,
+}
+
+impl JoinHandle {
+ /// `detach()` and `forget()` to remove any effects associated with this handle.
+ pub fn detach(mut self) {
+ self.disconnect();
+ self.forget();
+ }
+ /// Remove the handles capability to instruct the render thread to stop, but it will still wait for it
+ /// if dropped.
+ /// Use `forget()` if it should not wait for the render thread anymore.
+ pub fn disconnect(&mut self) {
+ self.disconnected = true;
+ }
+ /// Remove the handles capability to `join()` by forgetting the threads handle
+ pub fn forget(&mut self) {
+ self.inner.take();
+ }
+ /// Wait for the thread to shutdown naturally, for example because there is no more progress to display
+ pub fn wait(mut self) {
+ self.inner.take().and_then(|h| h.join().ok());
+ }
+ /// Send the shutdown signal right after one last redraw
+ pub fn shutdown(&mut self) {
+ if !self.disconnected {
+ self.connection.send(Event::Tick).ok();
+ self.connection.send(Event::Quit).ok();
+ }
+ }
+ /// Send the signal to shutdown and wait for the thread to be shutdown.
+ pub fn shutdown_and_wait(mut self) {
+ self.shutdown();
+ self.wait();
+ }
+}
+
+impl Drop for JoinHandle {
+ fn drop(&mut self) {
+ self.shutdown();
+ self.inner.take().and_then(|h| h.join().ok());
+ }
+}
+
+#[derive(Debug)]
+enum Event {
+ Tick,
+ Quit,
+ #[cfg(feature = "signal-hook")]
+ Resize(u16, u16),
+}
+
+/// Write a line-based representation of `progress` to `out` which is assumed to be a terminal.
+///
+/// Configure it with `config`, see the [`Options`] for details.
+pub fn render(
+ mut out: impl io::Write + Send + 'static,
+ progress: impl WeakRoot + Send + 'static,
+ Options {
+ output_is_terminal,
+ colored,
+ timestamp,
+ level_filter,
+ terminal_dimensions,
+ initial_delay,
+ frames_per_second,
+ keep_running_if_progress_is_empty,
+ hide_cursor,
+ throughput,
+ }: Options,
+) -> JoinHandle {
+ #[cfg_attr(not(feature = "signal-hook"), allow(unused_mut))]
+ let mut config = draw::Options {
+ level_filter,
+ terminal_dimensions,
+ keep_running_if_progress_is_empty,
+ output_is_terminal,
+ colored,
+ timestamp,
+ hide_cursor,
+ };
+
+ let (event_send, event_recv) = std::sync::mpsc::sync_channel::<Event>(1);
+ let show_cursor = possibly_hide_cursor(&mut out, hide_cursor && output_is_terminal);
+ static SHOW_PROGRESS: AtomicBool = AtomicBool::new(false);
+ #[cfg(feature = "signal-hook")]
+ let term_signal_received: Arc<AtomicBool> = Arc::new(AtomicBool::new(false));
+ #[cfg(feature = "signal-hook")]
+ let terminal_resized: Arc<AtomicBool> = Arc::new(AtomicBool::new(false));
+ #[cfg(feature = "signal-hook")]
+ {
+ for sig in signal_hook::consts::TERM_SIGNALS {
+ signal_hook::flag::register(*sig, term_signal_received.clone()).ok();
+ }
+
+ #[cfg(unix)]
+ signal_hook::flag::register(signal_hook::consts::SIGWINCH, terminal_resized.clone()).ok();
+ }
+
+ let handle = std::thread::Builder::new()
+ .name("render-line-eventloop".into())
+ .spawn({
+ let tick_send = event_send.clone();
+ move || {
+ {
+ let initial_delay = initial_delay.unwrap_or_default();
+ SHOW_PROGRESS.store(initial_delay == Duration::default(), Ordering::Relaxed);
+ if !SHOW_PROGRESS.load(Ordering::Relaxed) {
+ std::thread::Builder::new()
+ .name("render-line-progress-delay".into())
+ .spawn(move || {
+ std::thread::sleep(initial_delay);
+ SHOW_PROGRESS.store(true, Ordering::Relaxed);
+ })
+ .ok();
+ }
+ }
+
+ let mut state = draw::State::default();
+ if throughput {
+ state.throughput = Some(Throughput::default());
+ }
+ let secs = 1.0 / frames_per_second;
+ let _ticker = std::thread::Builder::new()
+ .name("render-line-ticker".into())
+ .spawn(move || loop {
+ #[cfg(feature = "signal-hook")]
+ {
+ if term_signal_received.load(Ordering::SeqCst) {
+ tick_send.send(Event::Quit).ok();
+ break;
+ }
+ if terminal_resized.load(Ordering::SeqCst) {
+ terminal_resized.store(false, Ordering::SeqCst);
+ if let Ok((x, y)) = crosstermion::terminal::size() {
+ tick_send.send(Event::Resize(x, y)).ok();
+ }
+ }
+ }
+ if tick_send.send(Event::Tick).is_err() {
+ break;
+ }
+ std::thread::sleep(Duration::from_secs_f32(secs));
+ })
+ .expect("starting a thread works");
+
+ for event in event_recv {
+ match event {
+ #[cfg(feature = "signal-hook")]
+ Event::Resize(x, y) => {
+ config.terminal_dimensions = (x, y);
+ draw::all(&mut out, SHOW_PROGRESS.load(Ordering::Relaxed), &mut state, &config)?;
+ }
+ Event::Tick => match progress.upgrade() {
+ Some(progress) => {
+ let has_changed = state.update_from_progress(&progress);
+ draw::all(
+ &mut out,
+ SHOW_PROGRESS.load(Ordering::Relaxed) && has_changed,
+ &mut state,
+ &config,
+ )?;
+ }
+ None => {
+ state.clear();
+ draw::all(&mut out, SHOW_PROGRESS.load(Ordering::Relaxed), &mut state, &config)?;
+ break;
+ }
+ },
+ Event::Quit => {
+ state.clear();
+ draw::all(&mut out, SHOW_PROGRESS.load(Ordering::Relaxed), &mut state, &config)?;
+ break;
+ }
+ }
+ }
+
+ if show_cursor {
+ crosstermion::execute!(out, crosstermion::cursor::Show).ok();
+ }
+
+ // One day we might try this out on windows, but let's not risk it now.
+ #[cfg(unix)]
+ write!(out, "\x1b[2K\r").ok(); // clear the last line.
+ Ok(())
+ }
+ })
+ .expect("starting a thread works");
+
+ JoinHandle {
+ inner: Some(handle),
+ connection: event_send,
+ disconnected: false,
+ }
+}
+
+// Not all configurations actually need it to be mut, but those with the 'signal-hook' feature do
+#[allow(unused_mut)]
+fn possibly_hide_cursor(out: &mut impl io::Write, mut hide_cursor: bool) -> bool {
+ if hide_cursor {
+ crosstermion::execute!(out, crosstermion::cursor::Hide).is_ok()
+ } else {
+ false
+ }
+}
diff --git a/vendor/prodash/src/render/line/mod.rs b/vendor/prodash/src/render/line/mod.rs
new file mode 100644
index 000000000..37a49e242
--- /dev/null
+++ b/vendor/prodash/src/render/line/mod.rs
@@ -0,0 +1,10 @@
+#[cfg(all(
+ feature = "render-line",
+ not(any(feature = "render-line-crossterm", feature = "render-line-termion"))
+))]
+compile_error!("Please choose either one of these features: 'render-line-crossterm' or 'render-line-termion'");
+
+mod draw;
+mod engine;
+
+pub use engine::{render, JoinHandle, Options, StreamKind};
diff --git a/vendor/prodash/src/render/mod.rs b/vendor/prodash/src/render/mod.rs
new file mode 100644
index 000000000..f1780df7d
--- /dev/null
+++ b/vendor/prodash/src/render/mod.rs
@@ -0,0 +1,11 @@
+#[cfg(feature = "render-tui")]
+///
+pub mod tui;
+#[cfg(feature = "render-tui")]
+pub use self::tui::render as tui;
+
+#[cfg(feature = "render-line")]
+///
+pub mod line;
+#[cfg(feature = "render-line")]
+pub use self::line::render as line;
diff --git a/vendor/prodash/src/render/tui/draw/all.rs b/vendor/prodash/src/render/tui/draw/all.rs
new file mode 100644
index 000000000..74a3bc464
--- /dev/null
+++ b/vendor/prodash/src/render/tui/draw/all.rs
@@ -0,0 +1,164 @@
+use std::time::Duration;
+
+use tui::{
+ buffer::Buffer,
+ layout::Rect,
+ style::{Modifier, Style},
+ text::Span,
+ widgets::{Block, Borders, Widget},
+};
+
+use crate::{
+ messages::Message,
+ progress::{Key, Task},
+ render::tui::{
+ draw,
+ utils::{block_width, rect},
+ InterruptDrawInfo, Line,
+ },
+ Throughput,
+};
+
+#[derive(Default)]
+pub struct State {
+ pub title: String,
+ pub task_offset: u16,
+ pub message_offset: u16,
+ pub hide_messages: bool,
+ pub messages_fullscreen: bool,
+ pub user_provided_window_size: Option<Rect>,
+ pub duration_per_frame: Duration,
+ pub information: Vec<Line>,
+ pub hide_info: bool,
+ pub maximize_info: bool,
+ pub last_tree_column_width: Option<u16>,
+ pub next_tree_column_width: Option<u16>,
+ pub throughput: Option<Throughput>,
+}
+
+pub(crate) fn all(
+ state: &mut State,
+ interrupt_mode: InterruptDrawInfo,
+ entries: &[(Key, Task)],
+ messages: &[Message],
+ bound: Rect,
+ buf: &mut Buffer,
+) {
+ let (bound, info_pane) = compute_info_bound(
+ bound,
+ if state.hide_info { &[] } else { &state.information },
+ state.maximize_info,
+ );
+ let bold = Style::default().add_modifier(Modifier::BOLD);
+ let window = Block::default()
+ .title(Span::styled(state.title.as_str(), bold))
+ .borders(Borders::ALL);
+ let inner_area = window.inner(bound);
+ window.render(bound, buf);
+ if bound.width < 4 || bound.height < 4 {
+ return;
+ }
+
+ let border_width = 1;
+ draw::progress::headline(
+ entries,
+ interrupt_mode,
+ state.duration_per_frame,
+ buf,
+ rect::offset_x(
+ Rect {
+ height: 1,
+ width: bound.width.saturating_sub(border_width),
+ ..bound
+ },
+ block_width(&state.title) + (border_width * 2),
+ ),
+ );
+
+ let (progress_pane, messages_pane) = compute_pane_bounds(
+ if state.hide_messages { &[] } else { messages },
+ inner_area,
+ state.messages_fullscreen,
+ );
+
+ draw::progress::pane(entries, progress_pane, buf, state);
+ if let Some(messages_pane) = messages_pane {
+ draw::messages::pane(
+ messages,
+ messages_pane,
+ Rect {
+ width: messages_pane.width + 2,
+ ..rect::line_bound(bound, bound.height.saturating_sub(1) as usize)
+ },
+ &mut state.message_offset,
+ buf,
+ );
+ }
+
+ if let Some(info_pane) = info_pane {
+ draw::information::pane(&state.information, info_pane, buf);
+ }
+}
+
+fn compute_pane_bounds(messages: &[Message], inner: Rect, messages_fullscreen: bool) -> (Rect, Option<Rect>) {
+ if messages.is_empty() {
+ (inner, None)
+ } else {
+ let (task_percent, messages_percent) = if messages_fullscreen { (0.1, 0.9) } else { (0.75, 0.25) };
+ let tasks_height: u16 = (inner.height as f32 * task_percent).ceil() as u16;
+ let messages_height: u16 = (inner.height as f32 * messages_percent).floor() as u16;
+ if messages_height < 2 {
+ (inner, None)
+ } else {
+ let messages_title = 1u16;
+ let new_messages_height = messages_height.min((messages.len() + messages_title as usize) as u16);
+ let tasks_height = tasks_height.saturating_add(messages_height - new_messages_height);
+ let messages_height = new_messages_height;
+ (
+ Rect {
+ height: tasks_height,
+ ..inner
+ },
+ Some(rect::intersect(
+ Rect {
+ y: tasks_height + messages_title,
+ height: messages_height,
+ ..inner
+ },
+ inner,
+ )),
+ )
+ }
+ }
+}
+
+fn compute_info_bound(bound: Rect, info: &[Line], maximize: bool) -> (Rect, Option<Rect>) {
+ if info.is_empty() {
+ return (bound, None);
+ }
+ let margin = 1;
+ let max_line_width = info.iter().fold(0, |state, l| {
+ state.max(
+ block_width(match l {
+ Line::Text(s) | Line::Title(s) => s,
+ }) + margin * 2,
+ )
+ });
+ let pane_width = if maximize {
+ bound.width.saturating_sub(8).min(max_line_width)
+ } else {
+ (bound.width / 3).min(max_line_width)
+ };
+
+ if pane_width < max_line_width / 3 {
+ return (bound, None);
+ }
+
+ (
+ Rect {
+ width: bound.width.saturating_sub(pane_width),
+ ..bound
+ },
+ Some(rect::snap_to_right(bound, pane_width)),
+ )
+}
diff --git a/vendor/prodash/src/render/tui/draw/information.rs b/vendor/prodash/src/render/tui/draw/information.rs
new file mode 100644
index 000000000..0d5f7a76e
--- /dev/null
+++ b/vendor/prodash/src/render/tui/draw/information.rs
@@ -0,0 +1,61 @@
+use tui::{
+ buffer::Buffer,
+ layout::Rect,
+ style::{Modifier, Style},
+ text::Span,
+ widgets::{Block, Borders, Widget},
+};
+
+use crate::render::tui::{
+ utils::{block_width, draw_text_with_ellipsis_nowrap, rect},
+ Line,
+};
+
+pub fn pane(lines: &[Line], bound: Rect, buf: &mut Buffer) {
+ let bold = Style::default().add_modifier(Modifier::BOLD);
+ let block = Block::default()
+ .title(Span::styled("Information", bold))
+ .borders(Borders::TOP | Borders::BOTTOM);
+ let inner_bound = block.inner(bound);
+ block.render(bound, buf);
+
+ let help_text = " ⨯ = [ | ▢ = { ";
+ draw_text_with_ellipsis_nowrap(rect::snap_to_right(bound, block_width(help_text)), buf, help_text, bold);
+
+ let bound = Rect {
+ width: inner_bound.width.saturating_sub(1),
+ ..inner_bound
+ };
+ let mut offset = 0;
+ for (line, info) in lines.windows(2).enumerate() {
+ let (info, next_info) = (&info[0], &info[1]);
+ let line = line + offset;
+ if line >= bound.height as usize {
+ break;
+ }
+ let line_bound = rect::line_bound(bound, line);
+ match info {
+ Line::Title(text) => {
+ let blocks_drawn = draw_text_with_ellipsis_nowrap(line_bound, buf, text, bold);
+ let lines_rect = rect::offset_x(line_bound, blocks_drawn + 1);
+ for x in lines_rect.left()..lines_rect.right() {
+ buf.get_mut(x, lines_rect.y).symbol = "─".into();
+ }
+ offset += 1;
+ }
+ Line::Text(text) => {
+ draw_text_with_ellipsis_nowrap(rect::offset_x(line_bound, 1), buf, text, None);
+ }
+ };
+ if let Line::Title(_) = next_info {
+ offset += 1;
+ }
+ }
+
+ if let Some(Line::Text(text)) = lines.last() {
+ let line = lines.len().saturating_sub(1) + offset;
+ if line < bound.height as usize {
+ draw_text_with_ellipsis_nowrap(rect::offset_x(rect::line_bound(bound, line), 1), buf, text, bold);
+ }
+ }
+}
diff --git a/vendor/prodash/src/render/tui/draw/messages.rs b/vendor/prodash/src/render/tui/draw/messages.rs
new file mode 100644
index 000000000..c493a7387
--- /dev/null
+++ b/vendor/prodash/src/render/tui/draw/messages.rs
@@ -0,0 +1,150 @@
+use std::time::SystemTime;
+
+use tui::{
+ buffer::Buffer,
+ layout::Rect,
+ style::{Color, Modifier, Style},
+ text::Span,
+ widgets::{Block, Borders, Widget},
+};
+use unicode_width::UnicodeWidthStr;
+
+use crate::{
+ messages::{Message, MessageLevel},
+ render::tui::utils::{block_width, draw_text_with_ellipsis_nowrap, rect, sanitize_offset, VERTICAL_LINE},
+ time::{format_time_for_messages, DATE_TIME_HMS},
+};
+
+pub fn pane(messages: &[Message], bound: Rect, overflow_bound: Rect, offset: &mut u16, buf: &mut Buffer) {
+ let bold = Style::default().add_modifier(Modifier::BOLD);
+ let block = Block::default()
+ .title(Span::styled("Messages", bold))
+ .borders(Borders::TOP);
+ let inner_bound = block.inner(bound);
+ block.render(bound, buf);
+ let help_text = " ⨯ = `| ▢ = ~ ";
+ draw_text_with_ellipsis_nowrap(rect::snap_to_right(bound, block_width(help_text)), buf, help_text, bold);
+
+ let bound = inner_bound;
+ *offset = sanitize_offset(*offset, messages.len(), bound.height);
+ let max_origin_width = messages
+ .iter()
+ .rev()
+ .skip(*offset as usize)
+ .take(bound.height as usize)
+ .fold(0, |state, message| state.max(block_width(&message.origin)));
+ for (
+ line,
+ Message {
+ time,
+ message,
+ level,
+ origin,
+ },
+ ) in messages
+ .iter()
+ .rev()
+ .skip(*offset as usize)
+ .take(bound.height as usize)
+ .enumerate()
+ {
+ let line_bound = rect::line_bound(bound, line);
+ let (time_bound, level_bound, origin_bound, message_bound) = compute_bounds(line_bound, max_origin_width);
+ if let Some(time_bound) = time_bound {
+ draw_text_with_ellipsis_nowrap(time_bound, buf, format_time_column(time), None);
+ }
+ if let Some(level_bound) = level_bound {
+ draw_text_with_ellipsis_nowrap(
+ level_bound,
+ buf,
+ format_level_column(*level),
+ Some(level_to_style(*level)),
+ );
+ draw_text_with_ellipsis_nowrap(rect::offset_x(level_bound, LEVEL_TEXT_WIDTH), buf, VERTICAL_LINE, None);
+ }
+ if let Some(origin_bound) = origin_bound {
+ draw_text_with_ellipsis_nowrap(origin_bound, buf, origin, None);
+ draw_text_with_ellipsis_nowrap(rect::offset_x(origin_bound, max_origin_width), buf, "→", None);
+ }
+ draw_text_with_ellipsis_nowrap(message_bound, buf, message, None);
+ }
+
+ if (bound.height as usize) < messages.len().saturating_sub(*offset as usize)
+ || (*offset).min(messages.len() as u16) > 0
+ {
+ let messages_below = messages
+ .len()
+ .saturating_sub(bound.height.saturating_add(*offset) as usize);
+ let messages_skipped = (*offset).min(messages.len() as u16);
+ draw_text_with_ellipsis_nowrap(
+ rect::offset_x(overflow_bound, 1),
+ buf,
+ format!("… {} skipped and {} more", messages_skipped, messages_below),
+ bold,
+ );
+ let help_text = " ⇊ = D|↓ = J|⇈ = U|↑ = K ┘";
+ draw_text_with_ellipsis_nowrap(
+ rect::snap_to_right(overflow_bound, block_width(help_text)),
+ buf,
+ help_text,
+ bold,
+ );
+ }
+}
+
+const LEVEL_TEXT_WIDTH: u16 = 4;
+fn format_level_column(level: MessageLevel) -> &'static str {
+ use MessageLevel::*;
+ match level {
+ Info => "info",
+ Failure => "fail",
+ Success => "done",
+ }
+}
+
+fn level_to_style(level: MessageLevel) -> Style {
+ use MessageLevel::*;
+ Style::default()
+ .fg(Color::Black)
+ .add_modifier(Modifier::BOLD)
+ .bg(match level {
+ Info => Color::White,
+ Failure => Color::Red,
+ Success => Color::Green,
+ })
+}
+
+fn format_time_column(time: &SystemTime) -> String {
+ format!("{}{}", format_time_for_messages(*time), VERTICAL_LINE)
+}
+
+fn compute_bounds(line: Rect, max_origin_width: u16) -> (Option<Rect>, Option<Rect>, Option<Rect>, Rect) {
+ let vertical_line_width = VERTICAL_LINE.width() as u16;
+ let mythical_offset_we_should_not_need = 1;
+
+ let time_bound = Rect {
+ width: DATE_TIME_HMS as u16 + vertical_line_width,
+ ..line
+ };
+
+ let mut cursor = time_bound.width + mythical_offset_we_should_not_need;
+ let level_bound = Rect {
+ x: cursor,
+ width: LEVEL_TEXT_WIDTH + vertical_line_width,
+ ..line
+ };
+ cursor += level_bound.width;
+
+ let origin_bound = Rect {
+ x: cursor,
+ width: max_origin_width + vertical_line_width,
+ ..line
+ };
+ cursor += origin_bound.width;
+
+ let message_bound = rect::intersect(rect::offset_x(line, cursor), line);
+ if message_bound.width < 30 {
+ return (None, None, None, line);
+ }
+ (Some(time_bound), Some(level_bound), Some(origin_bound), message_bound)
+}
diff --git a/vendor/prodash/src/render/tui/draw/mod.rs b/vendor/prodash/src/render/tui/draw/mod.rs
new file mode 100644
index 000000000..903f0d081
--- /dev/null
+++ b/vendor/prodash/src/render/tui/draw/mod.rs
@@ -0,0 +1,6 @@
+mod all;
+mod information;
+mod messages;
+mod progress;
+
+pub(crate) use all::{all, State};
diff --git a/vendor/prodash/src/render/tui/draw/progress.rs b/vendor/prodash/src/render/tui/draw/progress.rs
new file mode 100644
index 000000000..acabac5d5
--- /dev/null
+++ b/vendor/prodash/src/render/tui/draw/progress.rs
@@ -0,0 +1,506 @@
+use std::{
+ fmt,
+ sync::atomic::Ordering,
+ time::{Duration, SystemTime},
+};
+
+use humantime::format_duration;
+use tui::{
+ buffer::Buffer,
+ layout::Rect,
+ style::{Color, Modifier, Style},
+};
+use tui_react::fill_background;
+
+use crate::{
+ progress::{self, Key, Step, Task, Value},
+ render::tui::{
+ draw::State,
+ utils::{
+ block_width, draw_text_nowrap_fn, draw_text_with_ellipsis_nowrap, rect, sanitize_offset,
+ GraphemeCountWriter, VERTICAL_LINE,
+ },
+ InterruptDrawInfo,
+ },
+ time::format_now_datetime_seconds,
+ unit, Throughput,
+};
+
+const MIN_TREE_WIDTH: u16 = 20;
+
+pub fn pane(entries: &[(Key, progress::Task)], mut bound: Rect, buf: &mut Buffer, state: &mut State) {
+ state.task_offset = sanitize_offset(state.task_offset, entries.len(), bound.height);
+ let needs_overflow_line =
+ if entries.len() > bound.height as usize || (state.task_offset).min(entries.len() as u16) > 0 {
+ bound.height = bound.height.saturating_sub(1);
+ true
+ } else {
+ false
+ };
+ state.task_offset = sanitize_offset(state.task_offset, entries.len(), bound.height);
+
+ if entries.is_empty() {
+ return;
+ }
+
+ let initial_column_width = bound.width / 3;
+ let desired_max_tree_draw_width = *state.next_tree_column_width.as_ref().unwrap_or(&initial_column_width);
+ {
+ if initial_column_width >= MIN_TREE_WIDTH {
+ let tree_bound = Rect {
+ width: desired_max_tree_draw_width,
+ ..bound
+ };
+ let computed = draw_tree(entries, buf, tree_bound, state.task_offset);
+ state.last_tree_column_width = Some(computed);
+ } else {
+ state.last_tree_column_width = Some(0);
+ };
+ }
+
+ {
+ if let Some(tp) = state.throughput.as_mut() {
+ tp.update_elapsed();
+ }
+
+ let progress_area = rect::offset_x(bound, desired_max_tree_draw_width);
+ draw_progress(
+ entries,
+ buf,
+ progress_area,
+ state.task_offset,
+ state.throughput.as_mut(),
+ );
+
+ if let Some(tp) = state.throughput.as_mut() {
+ tp.reconcile(entries);
+ }
+ }
+
+ if needs_overflow_line {
+ let overflow_rect = Rect {
+ y: bound.height + 1,
+ height: 1,
+ ..bound
+ };
+ draw_overflow(
+ entries,
+ buf,
+ overflow_rect,
+ desired_max_tree_draw_width,
+ bound.height,
+ state.task_offset,
+ );
+ }
+}
+
+pub(crate) fn headline(
+ entries: &[(Key, Task)],
+ interrupt_mode: InterruptDrawInfo,
+ duration_per_frame: Duration,
+ buf: &mut Buffer,
+ bound: Rect,
+) {
+ let (num_running_tasks, num_blocked_tasks, num_groups) = entries.iter().fold(
+ (0, 0, 0),
+ |(mut running, mut blocked, mut groups), (_key, Task { progress, .. })| {
+ match progress.as_ref().map(|p| p.state) {
+ Some(progress::State::Running) => running += 1,
+ Some(progress::State::Blocked(_, _)) | Some(progress::State::Halted(_, _)) => blocked += 1,
+ None => groups += 1,
+ }
+ (running, blocked, groups)
+ },
+ );
+ let text = format!(
+ " {} {} {:3} running + {:3} blocked + {:3} groups = {} ",
+ match interrupt_mode {
+ InterruptDrawInfo::Instantly => "'q' or CTRL+c to quit",
+ InterruptDrawInfo::Deferred(interrupt_requested) => {
+ if interrupt_requested {
+ "interrupt requested - please wait"
+ } else {
+ "cannot interrupt current operation"
+ }
+ }
+ },
+ if duration_per_frame > Duration::from_secs(1) {
+ format!(
+ " Every {}s → {}",
+ duration_per_frame.as_secs(),
+ format_now_datetime_seconds()
+ )
+ } else {
+ "".into()
+ },
+ num_running_tasks,
+ num_blocked_tasks,
+ num_groups,
+ entries.len()
+ );
+
+ let bold = Style::default().add_modifier(Modifier::BOLD);
+ draw_text_with_ellipsis_nowrap(rect::snap_to_right(bound, block_width(&text) + 1), buf, text, bold);
+}
+
+struct ProgressFormat<'a>(&'a Option<Value>, u16, Option<unit::display::Throughput>);
+
+impl<'a> fmt::Display for ProgressFormat<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self.0 {
+ Some(p) => match p.unit.as_ref() {
+ Some(unit) => write!(
+ f,
+ "{}",
+ unit.display(p.step.load(Ordering::SeqCst), p.done_at, self.2.clone())
+ ),
+ None => match p.done_at {
+ Some(done_at) => write!(f, "{}/{}", p.step.load(Ordering::SeqCst), done_at),
+ None => write!(f, "{}", p.step.load(Ordering::SeqCst)),
+ },
+ },
+ None => write!(f, "{:─<width$}", '─', width = self.1 as usize),
+ }
+ }
+}
+
+fn has_child(entries: &[(Key, Task)], index: usize) -> bool {
+ entries
+ .get(index + 1)
+ .and_then(|(other_key, other_val)| {
+ entries.get(index).map(|(cur_key, _)| {
+ cur_key.shares_parent_with(other_key, cur_key.level()) && other_val.progress.is_some()
+ })
+ })
+ .unwrap_or(false)
+}
+
+pub fn draw_progress(
+ entries: &[(Key, Task)],
+ buf: &mut Buffer,
+ bound: Rect,
+ offset: u16,
+ mut throughput: Option<&mut Throughput>,
+) {
+ let title_spacing = 2u16 + 1; // 2 on the left, 1 on the right
+ let max_progress_label_width = entries
+ .iter()
+ .skip(offset as usize)
+ .take(bound.height as usize)
+ .map(|(_, Task { progress, .. })| progress)
+ .fold(0, |state, progress| match progress {
+ progress @ Some(_) => {
+ use std::io::Write;
+ let mut w = GraphemeCountWriter::default();
+ write!(w, "{}", ProgressFormat(progress, 0, None)).expect("never fails");
+ state.max(w.0)
+ }
+ None => state,
+ });
+
+ for (
+ line,
+ (
+ entry_index,
+ (
+ key,
+ Task {
+ progress,
+ name: title,
+ id: _,
+ },
+ ),
+ ),
+ ) in entries
+ .iter()
+ .enumerate()
+ .skip(offset as usize)
+ .take(bound.height as usize)
+ .enumerate()
+ {
+ let throughput = throughput
+ .as_mut()
+ .and_then(|tp| tp.update_and_get(key, progress.as_ref()));
+ let line_bound = rect::line_bound(bound, line);
+ let progress_text = format!(
+ " {progress}",
+ progress = ProgressFormat(
+ progress,
+ if has_child(entries, entry_index) {
+ bound.width.saturating_sub(title_spacing)
+ } else {
+ 0
+ },
+ throughput
+ )
+ );
+
+ draw_text_with_ellipsis_nowrap(line_bound, buf, VERTICAL_LINE, None);
+
+ let tree_prefix = level_prefix(entries, entry_index);
+ let progress_rect = rect::offset_x(line_bound, block_width(&tree_prefix));
+ draw_text_with_ellipsis_nowrap(line_bound, buf, tree_prefix, None);
+ match progress
+ .as_ref()
+ .map(|p| (p.fraction(), p.state, p.step.load(Ordering::SeqCst)))
+ {
+ Some((Some(fraction), state, _step)) => {
+ let mut progress_text = progress_text;
+ add_block_eta(state, &mut progress_text);
+ let (bound, style) = draw_progress_bar_fn(buf, progress_rect, fraction, |fraction| match state {
+ progress::State::Blocked(_, _) => Color::Red,
+ progress::State::Halted(_, _) => Color::LightRed,
+ progress::State::Running => {
+ if fraction >= 0.8 {
+ Color::Green
+ } else {
+ Color::Yellow
+ }
+ }
+ });
+ let style_fn = move |_t: &str, x: u16, _y: u16| {
+ if x < bound.right() {
+ style
+ } else {
+ Style::default()
+ }
+ };
+ draw_text_nowrap_fn(progress_rect, buf, progress_text, style_fn);
+ }
+ Some((None, state, step)) => {
+ let mut progress_text = progress_text;
+ add_block_eta(state, &mut progress_text);
+ draw_text_with_ellipsis_nowrap(progress_rect, buf, progress_text, None);
+ let bar_rect = rect::offset_x(line_bound, max_progress_label_width as u16);
+ draw_spinner(
+ buf,
+ bar_rect,
+ step,
+ line,
+ match state {
+ progress::State::Blocked(_, _) => Color::Red,
+ progress::State::Halted(_, _) => Color::LightRed,
+ progress::State::Running => Color::White,
+ },
+ );
+ }
+ None => {
+ let bold = Style::default().add_modifier(Modifier::BOLD);
+ draw_text_nowrap_fn(progress_rect, buf, progress_text, |_, _, _| Style::default());
+ draw_text_with_ellipsis_nowrap(progress_rect, buf, format!(" {} ", title), bold);
+ }
+ }
+ }
+}
+
+fn add_block_eta(state: progress::State, progress_text: &mut String) {
+ match state {
+ progress::State::Blocked(reason, maybe_eta) | progress::State::Halted(reason, maybe_eta) => {
+ progress_text.push_str(" [");
+ progress_text.push_str(reason);
+ progress_text.push(']');
+ if let Some(eta) = maybe_eta {
+ let now = SystemTime::now();
+ if eta > now {
+ use std::fmt::Write;
+ write!(
+ progress_text,
+ " → {} to {}",
+ format_duration(eta.duration_since(now).expect("computation to work")),
+ if let progress::State::Blocked(_, _) = state {
+ "unblock"
+ } else {
+ "continue"
+ }
+ )
+ .expect("in-memory writes never fail");
+ }
+ }
+ }
+ progress::State::Running => {}
+ }
+}
+
+fn draw_spinner(buf: &mut Buffer, bound: Rect, step: Step, seed: usize, color: Color) {
+ if bound.width == 0 {
+ return;
+ }
+ let x = bound.x + ((step + seed) % bound.width as usize) as u16;
+ let width = 5;
+ let bound = rect::intersect(Rect { x, width, ..bound }, bound);
+ tui_react::fill_background(bound, buf, color);
+}
+
+fn draw_progress_bar_fn(
+ buf: &mut Buffer,
+ bound: Rect,
+ fraction: f32,
+ style: impl FnOnce(f32) -> Color,
+) -> (Rect, Style) {
+ if bound.width == 0 {
+ return (Rect::default(), Style::default());
+ }
+ let mut fractional_progress_rect = Rect {
+ width: ((bound.width as f32 * fraction).floor() as u16).min(bound.width),
+ ..bound
+ };
+ let color = style(fraction);
+ for y in fractional_progress_rect.top()..fractional_progress_rect.bottom() {
+ for x in fractional_progress_rect.left()..fractional_progress_rect.right() {
+ let cell = buf.get_mut(x, y);
+ cell.set_fg(color);
+ cell.set_symbol(tui::symbols::block::FULL);
+ }
+ }
+ if fractional_progress_rect.width < bound.width {
+ static BLOCK_SECTIONS: [&str; 9] = [
+ " ",
+ tui::symbols::block::ONE_EIGHTH,
+ tui::symbols::block::ONE_QUARTER,
+ tui::symbols::block::THREE_EIGHTHS,
+ tui::symbols::block::HALF,
+ tui::symbols::block::FIVE_EIGHTHS,
+ tui::symbols::block::THREE_QUARTERS,
+ tui::symbols::block::SEVEN_EIGHTHS,
+ tui::symbols::block::FULL,
+ ];
+ // Get the index based on how filled the remaining part is
+ let index = ((((bound.width as f32 * fraction) - fractional_progress_rect.width as f32) * 8f32).round()
+ as usize)
+ % BLOCK_SECTIONS.len();
+ let cell = buf.get_mut(fractional_progress_rect.right(), bound.y);
+ cell.set_symbol(BLOCK_SECTIONS[index]);
+ cell.set_fg(color);
+ fractional_progress_rect.width += 1;
+ }
+ (fractional_progress_rect, Style::default().bg(color).fg(Color::Black))
+}
+
+pub fn draw_tree(entries: &[(Key, Task)], buf: &mut Buffer, bound: Rect, offset: u16) -> u16 {
+ let mut max_prefix_len = 0;
+ for (line, (entry_index, entry)) in entries
+ .iter()
+ .enumerate()
+ .skip(offset as usize)
+ .take(bound.height as usize)
+ .enumerate()
+ {
+ let mut line_bound = rect::line_bound(bound, line);
+ line_bound.x = line_bound.x.saturating_sub(1);
+ line_bound.width = line_bound.width.saturating_sub(1);
+ let tree_prefix = format!("{} {} ", level_prefix(entries, entry_index), entry.1.name);
+ max_prefix_len = max_prefix_len.max(block_width(&tree_prefix));
+
+ let style = if entry.1.progress.is_none() {
+ Style::default().add_modifier(Modifier::BOLD).into()
+ } else {
+ None
+ };
+ draw_text_with_ellipsis_nowrap(line_bound, buf, tree_prefix, style);
+ }
+ max_prefix_len
+}
+
+fn level_prefix(entries: &[(Key, Task)], entry_index: usize) -> String {
+ let adj = Key::adjacency(entries, entry_index);
+ let key = entries[entry_index].0;
+ let key_level = key.level();
+ let is_orphan = adj.level() != key_level;
+ let mut buf = String::with_capacity(key_level as usize);
+ for level in 1..=key_level {
+ use crate::progress::key::SiblingLocation::*;
+ let is_child_level = level == key_level;
+ if level != 1 {
+ buf.push(' ');
+ }
+ if level == 1 && is_child_level {
+ buf.push(match adj[level] {
+ AboveAndBelow | Above => '├',
+ NotFound | Below => '│',
+ });
+ } else {
+ let c = if is_child_level {
+ match adj[level] {
+ NotFound => {
+ if is_orphan {
+ ' '
+ } else {
+ '·'
+ }
+ }
+ Above => '└',
+ Below => '┌',
+ AboveAndBelow => '├',
+ }
+ } else {
+ match adj[level] {
+ NotFound => {
+ if level == 1 {
+ '│'
+ } else if is_orphan {
+ '·'
+ } else {
+ ' '
+ }
+ }
+ Above => '└',
+ Below => '┌',
+ AboveAndBelow => '│',
+ }
+ };
+ buf.push(c)
+ }
+ }
+ buf
+}
+
+pub fn draw_overflow(
+ entries: &[(Key, Task)],
+ buf: &mut Buffer,
+ bound: Rect,
+ label_offset: u16,
+ num_entries_on_display: u16,
+ offset: u16,
+) {
+ let (count, mut progress_fraction) = entries
+ .iter()
+ .take(offset as usize)
+ .chain(entries.iter().skip((offset + num_entries_on_display) as usize))
+ .fold((0usize, 0f32), |(count, progress_fraction), (_key, value)| {
+ let progress = value.progress.as_ref().and_then(|p| p.fraction()).unwrap_or_default();
+ (count + 1, progress_fraction + progress)
+ });
+ progress_fraction /= count as f32;
+ let label = format!(
+ "{} …{} skipped and {} more",
+ if label_offset == 0 { "" } else { VERTICAL_LINE },
+ offset,
+ entries
+ .len()
+ .saturating_sub((offset + num_entries_on_display + 1) as usize)
+ );
+ let (progress_rect, style) = draw_progress_bar_fn(buf, bound, progress_fraction, |_| Color::Green);
+
+ let bg_color = Color::Red;
+ fill_background(rect::offset_x(bound, progress_rect.right() - 1), buf, bg_color);
+ let color_text_according_to_progress = move |_g: &str, x: u16, _y: u16| {
+ if x < progress_rect.right() {
+ style
+ } else {
+ style.bg(bg_color)
+ }
+ };
+ draw_text_nowrap_fn(
+ rect::offset_x(bound, label_offset),
+ buf,
+ label,
+ color_text_according_to_progress,
+ );
+ let help_text = "⇊ = d|↓ = j|⇈ = u|↑ = k ";
+ draw_text_nowrap_fn(
+ rect::snap_to_right(bound, block_width(help_text)),
+ buf,
+ help_text,
+ color_text_according_to_progress,
+ );
+}
diff --git a/vendor/prodash/src/render/tui/engine.rs b/vendor/prodash/src/render/tui/engine.rs
new file mode 100644
index 000000000..9658d02a7
--- /dev/null
+++ b/vendor/prodash/src/render/tui/engine.rs
@@ -0,0 +1,258 @@
+use std::{
+ io::{self, Write},
+ time::Duration,
+};
+
+use futures_lite::StreamExt;
+use tui::layout::Rect;
+
+use crate::{
+ render::tui::{draw, ticker},
+ Root, Throughput, WeakRoot,
+};
+
+/// Configure the terminal user interface
+#[derive(Clone)]
+pub struct Options {
+ /// The initial title to show for the whole window.
+ ///
+ /// Can be adjusted later by sending `Event::SetTitle(…)`
+ /// into the event stream, see see [`tui::render_with_input(…events)`](./fn.render_with_input.html) function.
+ pub title: String,
+ /// The amount of frames to draw per second. If below 1.0, it determines the amount of seconds between the frame.
+ ///
+ /// *e.g.* 1.0/4.0 is one frame every 4 seconds.
+ pub frames_per_second: f32,
+
+ /// If true, (default false), we will keep track of the previous progress state to derive
+ /// continuous throughput information from. Throughput will only show for units which have
+ /// explicitly enabled it, it is opt-in.
+ ///
+ /// This comes at the cost of additional memory and CPU time.
+ pub throughput: bool,
+
+ /// If set, recompute the column width of the task tree only every given frame. Otherwise the width will be recomputed every frame.
+ ///
+ /// Use this if there are many short-running tasks with varying names paired with high refresh rates of multiple frames per second to
+ /// stabilize the appearance of the TUI.
+ ///
+ /// For example, setting the value to 40 will with a frame rate of 20 per second will recompute the column width to fit all task names
+ /// every 2 seconds.
+ pub recompute_column_width_every_nth_frame: Option<usize>,
+ /// The initial window size.
+ ///
+ /// If unset, it will be retrieved from the current terminal.
+ pub window_size: Option<Rect>,
+
+ /// If true (default: true), we will stop running the TUI once the progress isn't available anymore (went out of scope).
+ pub stop_if_progress_missing: bool,
+}
+
+impl Default for Options {
+ fn default() -> Self {
+ Options {
+ title: "Progress Dashboard".into(),
+ frames_per_second: 10.0,
+ throughput: false,
+ recompute_column_width_every_nth_frame: None,
+ window_size: None,
+ stop_if_progress_missing: true,
+ }
+ }
+}
+
+/// A line as used in [`Event::SetInformation`](./enum.Event.html#variant.SetInformation)
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub enum Line {
+ /// Set a title with the given text
+ Title(String),
+ /// Set a line of text with the given content
+ Text(String),
+}
+
+/// The variants represented here allow the user to control when the GUI can be shutdown.
+#[derive(Debug, Clone, Copy)]
+pub enum Interrupt {
+ /// Immediately exit the GUI event loop when there is an interrupt request.
+ ///
+ /// This is the default when the event loop is entered.
+ Instantly,
+ /// Instead of exiting the event loop instantly, wait until the next Interrupt::Instantly
+ /// event is coming in.
+ Deferred,
+}
+
+#[derive(Clone, Copy)]
+pub(crate) enum InterruptDrawInfo {
+ Instantly,
+ /// Boolean signals if interrupt is requested
+ Deferred(bool),
+}
+
+#[cfg(not(any(feature = "render-tui-crossterm", feature = "render-tui-termion")))]
+compile_error!(
+ "Please set either the 'render-tui-crossterm' or 'render-tui-termion' feature whne using the 'render-tui'"
+);
+
+use crosstermion::{
+ input::{key_input_stream, Key},
+ terminal::{tui::new_terminal, AlternateRawScreen},
+};
+
+/// An event to be sent in the [`tui::render_with_input(…events)`](./fn.render_with_input.html) stream.
+///
+/// This way, the TUI can be instructed to draw frames or change the information to be displayed.
+#[derive(Debug, Clone)]
+pub enum Event {
+ /// Draw a frame
+ Tick,
+ /// Send any key - can be used to simulate user input, and is typically generated by the TUI's own input loop.
+ Input(Key),
+ /// Change the size of the window to the given rectangle.
+ ///
+ /// Useful to embed the TUI into other terminal user interfaces that can resize dynamically.
+ SetWindowSize(Rect),
+ /// Set the title of the progress dashboard
+ SetTitle(String),
+ /// Provide a list of titles and lines to populate the side bar on the right.
+ SetInformation(Vec<Line>),
+ /// The way the GUI will respond to interrupt requests. See `Interrupt` for more information.
+ SetInterruptMode(Interrupt),
+}
+
+/// Returns a future that draws the terminal user interface indefinitely.
+///
+/// * `progress` is the progress tree whose information to visualize.
+/// It will usually be changing constantly while the TUI holds it.
+/// * `options` are configuring the TUI.
+/// * `events` is a stream of `Event`s which manipulate the TUI while it is running
+///
+/// Failure may occour if there is no terminal to draw into.
+pub fn render_with_input(
+ out: impl std::io::Write,
+ progress: impl WeakRoot,
+ options: Options,
+ events: impl futures_core::Stream<Item = Event> + Send + Unpin,
+) -> Result<impl std::future::Future<Output = ()>, std::io::Error> {
+ let Options {
+ title,
+ frames_per_second,
+ window_size,
+ recompute_column_width_every_nth_frame,
+ throughput,
+ stop_if_progress_missing,
+ } = options;
+ let mut terminal = new_terminal(AlternateRawScreen::try_from(out)?)?;
+ terminal.hide_cursor()?;
+
+ let duration_per_frame = Duration::from_secs_f32(1.0 / frames_per_second);
+ let key_receive = key_input_stream();
+
+ let render_fut = async move {
+ let mut state = draw::State {
+ title,
+ duration_per_frame,
+ ..draw::State::default()
+ };
+ if throughput {
+ state.throughput = Some(Throughput::default());
+ }
+ let mut interrupt_mode = InterruptDrawInfo::Instantly;
+ let (entries_cap, messages_cap) = progress
+ .upgrade()
+ .map(|p| (p.num_tasks(), p.messages_capacity()))
+ .unwrap_or_default();
+ let mut entries = Vec::with_capacity(entries_cap);
+ let mut messages = Vec::with_capacity(messages_cap);
+ let mut events = ticker(duration_per_frame)
+ .map(|_| Event::Tick)
+ .or(key_receive.map(Event::Input))
+ .or(events);
+
+ let mut tick = 0usize;
+ let store_task_size_every = recompute_column_width_every_nth_frame.unwrap_or(1).max(1);
+ while let Some(event) = events.next().await {
+ let mut skip_redraw = false;
+ match event {
+ Event::Tick => {}
+ Event::Input(key) => match key {
+ Key::Esc | Key::Char('q') | Key::Ctrl('c') | Key::Ctrl('[') => match interrupt_mode {
+ InterruptDrawInfo::Instantly => break,
+ InterruptDrawInfo::Deferred(_) => interrupt_mode = InterruptDrawInfo::Deferred(true),
+ },
+ Key::Char('`') => state.hide_messages = !state.hide_messages,
+ Key::Char('~') => state.messages_fullscreen = !state.messages_fullscreen,
+ Key::Char('J') => state.message_offset = state.message_offset.saturating_add(1),
+ Key::Char('D') => state.message_offset = state.message_offset.saturating_add(10),
+ Key::Char('j') => state.task_offset = state.task_offset.saturating_add(1),
+ Key::Char('d') => state.task_offset = state.task_offset.saturating_add(10),
+ Key::Char('K') => state.message_offset = state.message_offset.saturating_sub(1),
+ Key::Char('U') => state.message_offset = state.message_offset.saturating_sub(10),
+ Key::Char('k') => state.task_offset = state.task_offset.saturating_sub(1),
+ Key::Char('u') => state.task_offset = state.task_offset.saturating_sub(10),
+ Key::Char('[') => state.hide_info = !state.hide_info,
+ Key::Char('{') => state.maximize_info = !state.maximize_info,
+ _ => skip_redraw = true,
+ },
+ Event::SetWindowSize(bound) => state.user_provided_window_size = Some(bound),
+ Event::SetTitle(title) => state.title = title,
+ Event::SetInformation(info) => state.information = info,
+ Event::SetInterruptMode(mode) => {
+ interrupt_mode = match mode {
+ Interrupt::Instantly => {
+ if let InterruptDrawInfo::Deferred(true) = interrupt_mode {
+ break;
+ }
+ InterruptDrawInfo::Instantly
+ }
+ Interrupt::Deferred => InterruptDrawInfo::Deferred(match interrupt_mode {
+ InterruptDrawInfo::Deferred(interrupt_requested) => interrupt_requested,
+ _ => false,
+ }),
+ };
+ }
+ }
+ if !skip_redraw {
+ tick += 1;
+
+ let progress = match progress.upgrade() {
+ Some(progress) => progress,
+ None if stop_if_progress_missing => break,
+ None => continue,
+ };
+ progress.sorted_snapshot(&mut entries);
+ if stop_if_progress_missing && entries.is_empty() {
+ break;
+ }
+ let terminal_window_size = terminal.pre_render().expect("pre-render to work");
+ let window_size = state
+ .user_provided_window_size
+ .or(window_size)
+ .unwrap_or(terminal_window_size);
+ let buf = terminal.current_buffer_mut();
+ if !state.hide_messages {
+ progress.copy_messages(&mut messages);
+ }
+
+ draw::all(&mut state, interrupt_mode, &entries, &messages, window_size, buf);
+ if tick == 1 || tick % store_task_size_every == 0 || state.last_tree_column_width.unwrap_or(0) == 0 {
+ state.next_tree_column_width = state.last_tree_column_width;
+ }
+ terminal.post_render().expect("post render to work");
+ }
+ }
+ // Make sure the terminal responds right away when this future stops, to reset back to the 'non-alternate' buffer
+ drop(terminal);
+ io::stdout().flush().ok();
+ };
+ Ok(render_fut)
+}
+
+/// An easy-to-use version of `render_with_input(…)` that does not allow state manipulation via an event stream.
+pub fn render(
+ out: impl std::io::Write,
+ progress: impl WeakRoot,
+ config: Options,
+) -> Result<impl std::future::Future<Output = ()>, std::io::Error> {
+ render_with_input(out, progress, config, futures_lite::stream::pending())
+}
diff --git a/vendor/prodash/src/render/tui/mod.rs b/vendor/prodash/src/render/tui/mod.rs
new file mode 100644
index 000000000..07c0732ad
--- /dev/null
+++ b/vendor/prodash/src/render/tui/mod.rs
@@ -0,0 +1,59 @@
+/*!
+* A module implementing a *terminal user interface* capable of visualizing all information stored in
+* [progress trees](../tree/struct.Root.html).
+*
+* **Please note** that it is behind the `render-tui` feature toggle, which is enabled by default.
+*
+* # Example
+*
+* ```should_panic
+* # fn main() -> Result<(), Box<dyn std::error::Error>> {
+* use futures::task::{LocalSpawnExt, SpawnExt};
+* use prodash::render::tui::ticker;
+* use prodash::Root;
+* // obtain a progress tree
+* let root = prodash::tree::Root::new();
+* // Configure the gui, provide it with a handle to the ever-changing tree
+* let render_fut = prodash::render::tui::render(
+* std::io::stdout(),
+* root.downgrade(),
+* prodash::render::tui::Options {
+* title: "minimal example".into(),
+* ..Default::default()
+* }
+* )?;
+* // As it runs forever, we want a way to stop it.
+* let (render_fut, abort_handle) = futures::future::abortable(render_fut);
+* let pool = futures::executor::LocalPool::new();
+* // Spawn the gui into the background…
+* let gui = pool.spawner().spawn_with_handle(async { render_fut.await.ok(); () })?;
+* // …and run tasks which provide progress
+* pool.spawner().spawn_local({
+* use futures::StreamExt;
+* let mut progress = root.add_child("task");
+* async move {
+* progress.init(None, None);
+* let mut count = 0;
+* let mut ticks = ticker(std::time::Duration::from_millis(100));
+* while let Some(_) = ticks.next().await {
+* progress.set(count);
+* count += 1;
+* }
+* }
+* })?;
+* // …when we are done, tell the GUI to stop
+* abort_handle.abort();
+* //…and wait until it is done
+* futures::executor::block_on(gui);
+* # Ok(())
+* # }
+* ```
+*/
+mod draw;
+mod engine;
+mod utils;
+
+pub use engine::*;
+/// Useful for bringing up the TUI without bringing in the `tui` crate yourself
+pub use tui as tui_export;
+pub use utils::ticker;
diff --git a/vendor/prodash/src/render/tui/utils.rs b/vendor/prodash/src/render/tui/utils.rs
new file mode 100644
index 000000000..7a261edce
--- /dev/null
+++ b/vendor/prodash/src/render/tui/utils.rs
@@ -0,0 +1,25 @@
+use std::{future::Future, pin::Pin, task::Poll, time::Duration};
+
+use async_io::Timer;
+
+/// Returns a stream of 'ticks', each being duration `dur` apart.
+///
+/// Can be useful to provide the TUI with additional events in regular intervals,
+/// when using the [`tui::render_with_input(…events)`](./fn.render_with_input.html) function.
+pub fn ticker(dur: Duration) -> impl futures_core::Stream<Item = ()> {
+ let mut delay = Timer::after(dur);
+ futures_lite::stream::poll_fn(move |ctx| {
+ let res = Pin::new(&mut delay).poll(ctx);
+ match res {
+ Poll::Pending => Poll::Pending,
+ Poll::Ready(_) => {
+ delay = Timer::after(dur);
+ Poll::Ready(Some(()))
+ }
+ }
+ })
+}
+
+pub const VERTICAL_LINE: &str = "│";
+
+pub use tui_react::{draw_text_nowrap_fn, draw_text_with_ellipsis_nowrap, util::*};
diff --git a/vendor/prodash/src/throughput.rs b/vendor/prodash/src/throughput.rs
new file mode 100644
index 000000000..6e8eebbd1
--- /dev/null
+++ b/vendor/prodash/src/throughput.rs
@@ -0,0 +1,124 @@
+use std::{
+ collections::VecDeque,
+ sync::atomic::Ordering,
+ time::{Duration, SystemTime},
+};
+
+use crate::{progress, unit};
+
+const THROTTLE_INTERVAL: Duration = Duration::from_secs(1);
+const ONCE_A_SECOND: Duration = Duration::from_secs(1);
+
+#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
+struct State {
+ observed: Duration,
+ last_value: progress::Step,
+ elapsed_values: VecDeque<(Duration, progress::Step)>,
+
+ last_update_duration: Duration,
+ precomputed_throughput: Option<progress::Step>,
+}
+
+impl State {
+ fn new(value: progress::Step, elapsed: Duration) -> Self {
+ State {
+ observed: elapsed,
+ last_value: value,
+ elapsed_values: {
+ let mut v = VecDeque::with_capacity(6); // default frames per second
+ v.push_back((elapsed, value));
+ v
+ },
+
+ last_update_duration: elapsed,
+ precomputed_throughput: None,
+ }
+ }
+
+ fn compute_throughput(&mut self) -> progress::Step {
+ let mut observed: Duration = self.elapsed_values.iter().map(|e| e.0).sum();
+ while !self.elapsed_values.is_empty() && observed > ONCE_A_SECOND {
+ let candidate = self
+ .elapsed_values
+ .front()
+ .map(|e| e.0)
+ .expect("at least one item as we are in the checked loop");
+ if observed.checked_sub(candidate).unwrap_or_default() <= ONCE_A_SECOND {
+ break;
+ }
+ observed -= candidate;
+ self.elapsed_values.pop_front();
+ }
+ let observed_value: progress::Step = self.elapsed_values.iter().map(|e| e.1).sum();
+ ((observed_value as f64 / observed.as_secs_f64()) * ONCE_A_SECOND.as_secs_f64()) as progress::Step
+ }
+
+ fn update(&mut self, value: progress::Step, elapsed: Duration) -> Option<unit::display::Throughput> {
+ self.observed += elapsed;
+ self.elapsed_values
+ .push_back((elapsed, value.saturating_sub(self.last_value)));
+ self.last_value = value;
+ if self.observed - self.last_update_duration > THROTTLE_INTERVAL {
+ self.precomputed_throughput = Some(self.compute_throughput());
+ self.last_update_duration = self.observed;
+ }
+ self.throughput()
+ }
+
+ fn throughput(&self) -> Option<unit::display::Throughput> {
+ self.precomputed_throughput.map(|tp| unit::display::Throughput {
+ value_change_in_timespan: tp,
+ timespan: ONCE_A_SECOND,
+ })
+ }
+}
+
+/// A utility to compute throughput of a set of progress values usually available to a renderer.
+#[derive(Default)]
+pub struct Throughput {
+ sorted_by_key: Vec<(progress::Key, State)>,
+ updated_at: Option<SystemTime>,
+ elapsed: Option<Duration>,
+}
+
+impl Throughput {
+ /// Called at the beginning of the drawing of a renderer to remember at which time progress values are
+ /// going to be updated with [`update_and_get(…)`][Throughput::update_and_get()].
+ pub fn update_elapsed(&mut self) {
+ let now = SystemTime::now();
+ self.elapsed = self.updated_at.and_then(|then| now.duration_since(then).ok());
+ self.updated_at = Some(now);
+ }
+
+ /// Lookup or create the progress value at `key` and set its current `progress`, returning its computed
+ /// throughput.
+ pub fn update_and_get(
+ &mut self,
+ key: &progress::Key,
+ progress: Option<&progress::Value>,
+ ) -> Option<unit::display::Throughput> {
+ progress.and_then(|progress| {
+ self.elapsed
+ .and_then(|elapsed| match self.sorted_by_key.binary_search_by_key(key, |t| t.0) {
+ Ok(index) => self.sorted_by_key[index]
+ .1
+ .update(progress.step.load(Ordering::SeqCst), elapsed),
+ Err(index) => {
+ let state = State::new(progress.step.load(Ordering::SeqCst), elapsed);
+ let tp = state.throughput();
+ self.sorted_by_key.insert(index, (*key, state));
+ tp
+ }
+ })
+ })
+ }
+
+ /// Compare the keys in `sorted_values` with our internal state and remove all missing tasks from it.
+ ///
+ /// This should be called after [`update_and_get(…)`][Throughput::update_and_get()] to pick up removed/finished
+ /// progress.
+ pub fn reconcile(&mut self, sorted_values: &[(progress::Key, progress::Task)]) {
+ self.sorted_by_key
+ .retain(|(key, _)| sorted_values.binary_search_by_key(key, |e| e.0).is_ok());
+ }
+}
diff --git a/vendor/prodash/src/time.rs b/vendor/prodash/src/time.rs
new file mode 100644
index 000000000..43d9af10f
--- /dev/null
+++ b/vendor/prodash/src/time.rs
@@ -0,0 +1,63 @@
+#[cfg(feature = "local-time")]
+mod localtime {
+ use std::time::SystemTime;
+
+ /// Return a string representing the current date and time as localtime.
+ ///
+ /// Available with the `localtime` feature toggle.
+ pub fn format_now_datetime_seconds() -> String {
+ let t = time::OffsetDateTime::now_utc();
+ t.to_offset(time::UtcOffset::local_offset_at(t).unwrap_or(time::UtcOffset::UTC))
+ .format(&time::format_description::parse("%F %T").expect("format known to work"))
+ .expect("formatting always works")
+ }
+
+ /// Return a string representing the current time as localtime.
+ ///
+ /// Available with the `localtime` feature toggle.
+ pub fn format_time_for_messages(time: SystemTime) -> String {
+ time::OffsetDateTime::from(time)
+ .to_offset(time::UtcOffset::current_local_offset().unwrap_or(time::UtcOffset::UTC))
+ .format(&time::format_description::parse("[hour]:[minute]:[second]").expect("format known to work"))
+ .expect("formatting always works")
+ }
+}
+
+/// An `hours:minute:seconds` format.
+pub const DATE_TIME_HMS: usize = "00:51:45".len();
+
+#[cfg(not(feature = "local-time"))]
+mod utc {
+ use std::time::SystemTime;
+
+ use super::DATE_TIME_HMS;
+ const DATE_TIME_YMD: usize = "2020-02-13T".len();
+
+ /// Return a string representing the current date and time as UTC.
+ ///
+ /// Available without the `localtime` feature toggle.
+ pub fn format_time_for_messages(time: SystemTime) -> String {
+ String::from_utf8_lossy(
+ &humantime::format_rfc3339_seconds(time).to_string().as_bytes()
+ [DATE_TIME_YMD..DATE_TIME_YMD + DATE_TIME_HMS],
+ )
+ .into_owned()
+ }
+
+ /// Return a string representing the current time as UTC.
+ ///
+ /// Available without the `localtime` feature toggle.
+ pub fn format_now_datetime_seconds() -> String {
+ String::from_utf8_lossy(
+ &humantime::format_rfc3339_seconds(std::time::SystemTime::now())
+ .to_string()
+ .as_bytes()[.."2020-02-13T00:51:45".len()],
+ )
+ .into_owned()
+ }
+}
+
+#[cfg(feature = "local-time")]
+pub use localtime::*;
+#[cfg(not(feature = "local-time"))]
+pub use utc::*;
diff --git a/vendor/prodash/src/traits.rs b/vendor/prodash/src/traits.rs
new file mode 100644
index 000000000..a2a17c760
--- /dev/null
+++ b/vendor/prodash/src/traits.rs
@@ -0,0 +1,300 @@
+use std::time::Instant;
+
+use crate::{messages::MessageLevel, progress, progress::Id, Unit};
+
+/// A trait for describing hierarchical process.
+pub trait Progress: Send {
+ /// The type of progress returned by [`add_child()`][Progress::add_child()].
+ type SubProgress: Progress;
+
+ /// Adds a new child, whose parent is this instance, with the given `name`.
+ ///
+ /// This will make the child progress to appear contained in the parent progress.
+ /// Note that such progress does not have a stable identifier, which can be added
+ /// with [`add_child_with_id()`][Progress::add_child_with_id()] if desired.
+ fn add_child(&mut self, name: impl Into<String>) -> Self::SubProgress;
+
+ /// Adds a new child, whose parent is this instance, with the given `name` and `id`.
+ ///
+ /// This will make the child progress to appear contained in the parent progress, and it can be identified
+ /// using `id`.
+ fn add_child_with_id(&mut self, name: impl Into<String>, id: Id) -> Self::SubProgress;
+
+ /// Initialize the Item for receiving progress information.
+ ///
+ /// If `max` is `Some(…)`, it will be treated as upper bound. When progress is [set(…)](./struct.Item.html#method.set)
+ /// it should not exceed the given maximum.
+ /// If `max` is `None`, the progress is unbounded. Use this if the amount of work cannot accurately
+ /// be determined in advance.
+ ///
+ /// If `unit` is `Some(…)`, it is used for display purposes only. See `prodash::Unit` for more information.
+ ///
+ /// If both `unit` and `max` are `None`, the item will be reset to be equivalent to 'uninitialized'.
+ ///
+ /// If this method is never called, this `Progress` instance will serve as organizational unit, useful to add more structure
+ /// to the progress tree (e.g. a headline).
+ ///
+ /// **Note** that this method can be called multiple times, changing the bounded-ness and unit at will.
+ fn init(&mut self, max: Option<progress::Step>, unit: Option<Unit>);
+
+ /// Set the current progress to the given `step`. The cost of this call is negligible,
+ /// making manual throttling *not* necessary.
+ ///
+ /// **Note**: that this call has no effect unless `init(…)` was called before.
+ fn set(&mut self, step: progress::Step);
+
+ /// Returns the (cloned) unit associated with this Progress
+ fn unit(&self) -> Option<Unit> {
+ None
+ }
+
+ /// Returns the maximum about of items we expect, as provided with the `init(…)` call
+ fn max(&self) -> Option<progress::Step> {
+ None
+ }
+
+ /// Set the maximum value to `max` and return the old maximum value.
+ fn set_max(&mut self, _max: Option<progress::Step>) -> Option<progress::Step> {
+ None
+ }
+
+ /// Returns the current step, as controlled by `inc*(…)` calls
+ fn step(&self) -> progress::Step;
+
+ /// Increment the current progress to the given `step`.
+ /// The cost of this call is negligible, making manual throttling *not* necessary.
+ fn inc_by(&mut self, step: progress::Step);
+
+ /// Increment the current progress to the given 1. The cost of this call is negligible,
+ /// making manual throttling *not* necessary.
+ fn inc(&mut self) {
+ self.inc_by(1)
+ }
+
+ /// Set the name of the instance, altering the value given when crating it with `add_child(…)`
+ /// The progress is allowed to discard it.
+ fn set_name(&mut self, name: impl Into<String>);
+
+ /// Get the name of the instance as given when creating it with `add_child(…)`
+ /// The progress is allowed to not be named, thus there is no guarantee that a previously set names 'sticks'.
+ fn name(&self) -> Option<String>;
+
+ /// Get a stable identifier for the progress instance.
+ /// Note that it could be [unknown][crate::progress::UNKNOWN].
+ fn id(&self) -> Id;
+
+ /// Create a `message` of the given `level` and store it with the progress tree.
+ ///
+ /// Use this to provide additional,human-readable information about the progress
+ /// made, including indicating success or failure.
+ fn message(&mut self, level: MessageLevel, message: impl Into<String>);
+
+ /// If available, return an atomic counter for direct access to the underlying state.
+ ///
+ /// This is useful if multiple threads want to access the same progress, without the need
+ /// for provide each their own progress and aggregating the result.
+ fn counter(&self) -> Option<StepShared> {
+ None
+ }
+
+ /// Create a message providing additional information about the progress thus far.
+ fn info(&mut self, message: impl Into<String>) {
+ self.message(MessageLevel::Info, message)
+ }
+ /// Create a message indicating the task is done successfully
+ fn done(&mut self, message: impl Into<String>) {
+ self.message(MessageLevel::Success, message)
+ }
+ /// Create a message indicating the task failed
+ fn fail(&mut self, message: impl Into<String>) {
+ self.message(MessageLevel::Failure, message)
+ }
+ /// A shorthand to print throughput information
+ fn show_throughput(&mut self, start: Instant) {
+ let step = self.step();
+ match self.unit() {
+ Some(unit) => self.show_throughput_with(start, step, unit, MessageLevel::Info),
+ None => {
+ let elapsed = start.elapsed().as_secs_f32();
+ let steps_per_second = (step as f32 / elapsed) as progress::Step;
+ self.info(format!(
+ "done {} items in {:.02}s ({} items/s)",
+ step, elapsed, steps_per_second
+ ))
+ }
+ };
+ }
+
+ /// A shorthand to print throughput information, with the given step and unit, and message level.
+ fn show_throughput_with(&mut self, start: Instant, step: progress::Step, unit: Unit, level: MessageLevel) {
+ use std::fmt::Write;
+ let elapsed = start.elapsed().as_secs_f32();
+ let steps_per_second = (step as f32 / elapsed) as progress::Step;
+ let mut buf = String::with_capacity(128);
+ let unit = unit.as_display_value();
+ let push_unit = |buf: &mut String| {
+ buf.push(' ');
+ let len_before_unit = buf.len();
+ unit.display_unit(buf, step).ok();
+ if buf.len() == len_before_unit {
+ buf.pop();
+ }
+ };
+
+ buf.push_str("done ");
+ unit.display_current_value(&mut buf, step, None).ok();
+ push_unit(&mut buf);
+
+ buf.write_fmt(format_args!(" in {:.02}s (", elapsed)).ok();
+ unit.display_current_value(&mut buf, steps_per_second, None).ok();
+ push_unit(&mut buf);
+ buf.push_str("/s)");
+
+ self.message(level, buf);
+ }
+}
+
+use crate::{
+ messages::{Message, MessageCopyState},
+ progress::StepShared,
+};
+
+/// The top-level root as weak handle, which needs an upgrade to become a usable root.
+///
+/// If the underlying reference isn't present anymore, such upgrade will fail permanently.
+pub trait WeakRoot {
+ /// The type implementing the `Root` trait
+ type Root: Root;
+
+ /// Equivalent to `std::sync::Weak::upgrade()`.
+ fn upgrade(&self) -> Option<Self::Root>;
+}
+
+/// The top level of a progress task hierarchy, with `progress::Task`s identified with `progress::Key`s
+pub trait Root {
+ /// The type implementing the `WeakRoot` trait
+ type WeakRoot: WeakRoot;
+
+ /// Returns the maximum amount of messages we can keep before overwriting older ones.
+ fn messages_capacity(&self) -> usize;
+
+ /// Returns the current amount of tasks underneath the root, transitively.
+ /// **Note** that this is at most a guess as tasks can be added and removed in parallel.
+ fn num_tasks(&self) -> usize;
+
+ /// Copy the entire progress tree into the given `out` vector, so that
+ /// it can be traversed from beginning to end in order of hierarchy.
+ /// The `out` vec will be cleared automatically.
+ fn sorted_snapshot(&self, out: &mut Vec<(progress::Key, progress::Task)>);
+
+ /// Copy all messages from the internal ring buffer into the given `out`
+ /// vector. Messages are ordered from oldest to newest.
+ fn copy_messages(&self, out: &mut Vec<Message>);
+
+ /// Copy only new messages from the internal ring buffer into the given `out`
+ /// vector. Messages are ordered from oldest to newest.
+ fn copy_new_messages(&self, out: &mut Vec<Message>, prev: Option<MessageCopyState>) -> MessageCopyState;
+
+ /// Similar to `Arc::downgrade()`
+ fn downgrade(&self) -> Self::WeakRoot;
+}
+
+mod impls {
+ use std::{
+ ops::{Deref, DerefMut},
+ time::Instant,
+ };
+
+ use crate::{
+ messages::MessageLevel,
+ progress::{Id, Step, StepShared},
+ Progress, Unit,
+ };
+
+ impl<'a, T> Progress for &'a mut T
+ where
+ T: Progress,
+ {
+ type SubProgress = <T as Progress>::SubProgress;
+
+ fn add_child(&mut self, name: impl Into<String>) -> Self::SubProgress {
+ self.deref_mut().add_child(name)
+ }
+
+ fn add_child_with_id(&mut self, name: impl Into<String>, id: Id) -> Self::SubProgress {
+ self.deref_mut().add_child_with_id(name, id)
+ }
+
+ fn init(&mut self, max: Option<Step>, unit: Option<Unit>) {
+ self.deref_mut().init(max, unit)
+ }
+
+ fn set(&mut self, step: Step) {
+ self.deref_mut().set(step)
+ }
+
+ fn unit(&self) -> Option<Unit> {
+ self.deref().unit()
+ }
+
+ fn max(&self) -> Option<Step> {
+ self.deref().max()
+ }
+
+ fn set_max(&mut self, max: Option<Step>) -> Option<Step> {
+ self.deref_mut().set_max(max)
+ }
+
+ fn step(&self) -> Step {
+ self.deref().step()
+ }
+
+ fn inc_by(&mut self, step: Step) {
+ self.deref_mut().inc_by(step)
+ }
+
+ fn inc(&mut self) {
+ self.deref_mut().inc()
+ }
+
+ fn set_name(&mut self, name: impl Into<String>) {
+ self.deref_mut().set_name(name)
+ }
+
+ fn name(&self) -> Option<String> {
+ self.deref().name()
+ }
+
+ fn id(&self) -> Id {
+ todo!()
+ }
+
+ fn message(&mut self, level: MessageLevel, message: impl Into<String>) {
+ self.deref_mut().message(level, message)
+ }
+
+ fn counter(&self) -> Option<StepShared> {
+ self.deref().counter()
+ }
+
+ fn info(&mut self, message: impl Into<String>) {
+ self.deref_mut().info(message)
+ }
+
+ fn done(&mut self, message: impl Into<String>) {
+ self.deref_mut().done(message)
+ }
+
+ fn fail(&mut self, message: impl Into<String>) {
+ self.deref_mut().fail(message)
+ }
+
+ fn show_throughput(&mut self, start: Instant) {
+ self.deref_mut().show_throughput(start)
+ }
+
+ fn show_throughput_with(&mut self, start: Instant, step: Step, unit: Unit, level: MessageLevel) {
+ self.deref_mut().show_throughput_with(start, step, unit, level)
+ }
+ }
+}
diff --git a/vendor/prodash/src/tree/item.rs b/vendor/prodash/src/tree/item.rs
new file mode 100644
index 000000000..d8c4a7548
--- /dev/null
+++ b/vendor/prodash/src/tree/item.rs
@@ -0,0 +1,406 @@
+use std::{
+ fmt::Debug,
+ ops::Deref,
+ sync::{
+ atomic::{AtomicUsize, Ordering},
+ Arc,
+ },
+ time::SystemTime,
+};
+
+use parking_lot::Mutex;
+
+use crate::{
+ messages::MessageLevel,
+ progress::{Id, State, Step, StepShared, Task, Value},
+ tree::Item,
+ unit::Unit,
+};
+
+impl Drop for Item {
+ fn drop(&mut self) {
+ self.tree.remove(&self.key);
+ }
+}
+
+impl Debug for Item {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("Item")
+ .field("key", &self.key)
+ .field("value", &self.value)
+ .finish_non_exhaustive()
+ }
+}
+
+impl Item {
+ /// Initialize the Item for receiving progress information.
+ ///
+ /// If `max` is `Some(…)`, it will be treated as upper bound. When progress is [set(…)](./struct.Item.html#method.set)
+ /// it should not exceed the given maximum.
+ /// If `max` is `None`, the progress is unbounded. Use this if the amount of work cannot accurately
+ /// be determined.
+ ///
+ /// If `unit` is `Some(…)`, it is used for display purposes only. It should be using the plural.
+ ///
+ /// If this method is never called, this `Item` will serve as organizational unit, useful to add more structure
+ /// to the progress tree.
+ ///
+ /// **Note** that this method can be called multiple times, changing the bounded-ness and unit at will.
+ pub fn init(&mut self, max: Option<usize>, unit: Option<Unit>) {
+ #[cfg(feature = "progress-tree-hp-hashmap")]
+ {
+ if let Some(mut r) = self.tree.get_mut(&self.key) {
+ self.value.store(0, Ordering::SeqCst);
+ r.value_mut().progress = (max.is_some() || unit.is_some()).then(|| Value {
+ done_at: max,
+ unit,
+ step: Arc::clone(&self.value),
+ ..Default::default()
+ })
+ };
+ }
+ #[cfg(not(feature = "progress-tree-hp-hashmap"))]
+ {
+ self.tree.get_mut(&self.key, |v| {
+ self.value.store(0, Ordering::SeqCst);
+ v.progress = (max.is_some() || unit.is_some()).then(|| Value {
+ done_at: max,
+ unit,
+ step: Arc::clone(&self.value),
+ ..Default::default()
+ });
+ });
+ }
+ }
+
+ fn alter_progress(&mut self, f: impl FnMut(&mut Value)) {
+ #[cfg(feature = "progress-tree-hp-hashmap")]
+ {
+ if let Some(mut r) = self.tree.get_mut(&self.key) {
+ // NOTE: since we wrap around, if there are more tasks than we can have IDs for,
+ // and if all these tasks are still alive, two progress trees may see the same ID
+ // when these go out of scope, they delete the key and the other tree will not find
+ // its value anymore. Besides, it's probably weird to see tasks changing their progress
+ // all the time…
+ r.value_mut().progress.as_mut().map(f);
+ };
+ }
+ #[cfg(not(feature = "progress-tree-hp-hashmap"))]
+ {
+ self.tree.get_mut(&self.key, |v| {
+ v.progress.as_mut().map(f);
+ });
+ }
+ }
+
+ /// Set the name of this task's progress to the given `name`.
+ pub fn set_name(&mut self, name: impl Into<String>) {
+ #[cfg(feature = "progress-tree-hp-hashmap")]
+ {
+ if let Some(mut r) = self.tree.get_mut(&self.key) {
+ r.value_mut().name = name.into();
+ };
+ }
+ #[cfg(not(feature = "progress-tree-hp-hashmap"))]
+ {
+ self.tree.get_mut(&self.key, |v| {
+ v.name = name.into();
+ });
+ }
+ }
+
+ /// Get the name of this task's progress
+ pub fn name(&self) -> Option<String> {
+ #[cfg(feature = "progress-tree-hp-hashmap")]
+ {
+ self.tree.get(&self.key).map(|r| r.value().name.to_owned())
+ }
+ #[cfg(not(feature = "progress-tree-hp-hashmap"))]
+ {
+ self.tree.get(&self.key, |v| v.name.to_owned())
+ }
+ }
+
+ /// Get the stable identifier of this instance.
+ pub fn id(&self) -> Id {
+ #[cfg(feature = "progress-tree-hp-hashmap")]
+ {
+ self.tree
+ .get(&self.key)
+ .map(|r| r.value().id)
+ .unwrap_or(crate::progress::UNKNOWN)
+ }
+ #[cfg(not(feature = "progress-tree-hp-hashmap"))]
+ {
+ self.tree.get(&self.key, |v| v.id).unwrap_or(crate::progress::UNKNOWN)
+ }
+ }
+
+ /// Returns the current step, as controlled by `inc*(…)` calls
+ pub fn step(&self) -> Option<Step> {
+ self.value.load(Ordering::Relaxed).into()
+ }
+
+ /// Returns the maximum about of items we expect, as provided with the `init(…)` call
+ pub fn max(&self) -> Option<Step> {
+ #[cfg(feature = "progress-tree-hp-hashmap")]
+ {
+ self.tree
+ .get(&self.key)
+ .and_then(|r| r.value().progress.as_ref().and_then(|p| p.done_at))
+ }
+ #[cfg(not(feature = "progress-tree-hp-hashmap"))]
+ {
+ self.tree
+ .get(&self.key, |v| v.progress.as_ref().and_then(|p| p.done_at))
+ .flatten()
+ }
+ }
+
+ /// Set the maximum value to `max` and return the old maximum value.
+ pub fn set_max(&mut self, max: Option<Step>) -> Option<Step> {
+ #[cfg(feature = "progress-tree-hp-hashmap")]
+ {
+ self.tree
+ .get_mut(&self.key)?
+ .value_mut()
+ .progress
+ .as_mut()
+ .and_then(|mut p| {
+ let prev = p.done_at;
+ p.done_at = max;
+ prev
+ })
+ }
+ #[cfg(not(feature = "progress-tree-hp-hashmap"))]
+ {
+ self.tree
+ .get_mut(&self.key, |v| {
+ v.progress.as_mut().and_then(|mut p| {
+ let prev = p.done_at;
+ p.done_at = max;
+ prev
+ })
+ })
+ .flatten()
+ }
+ }
+
+ /// Returns the (cloned) unit associated with this Progress
+ pub fn unit(&self) -> Option<Unit> {
+ #[cfg(feature = "progress-tree-hp-hashmap")]
+ {
+ self.tree
+ .get(&self.key)
+ .and_then(|r| r.value().progress.as_ref().and_then(|p| p.unit.clone()))
+ }
+ #[cfg(not(feature = "progress-tree-hp-hashmap"))]
+ {
+ self.tree
+ .get(&self.key, |v| v.progress.as_ref().and_then(|p| p.unit.clone()))
+ .flatten()
+ }
+ }
+
+ /// Set the current progress to the given `step`.
+ ///
+ /// **Note**: that this call has no effect unless `init(…)` was called before.
+ pub fn set(&mut self, step: Step) {
+ self.value.store(step, Ordering::SeqCst);
+ }
+
+ /// Increment the current progress by the given `step`.
+ ///
+ /// **Note**: that this call has no effect unless `init(…)` was called before.
+ pub fn inc_by(&mut self, step: Step) {
+ self.value.fetch_add(step, Ordering::SeqCst);
+ }
+
+ /// Increment the current progress by one.
+ ///
+ /// **Note**: that this call has no effect unless `init(…)` was called before.
+ pub fn inc(&mut self) {
+ self.value.fetch_add(1, Ordering::SeqCst);
+ }
+
+ /// Call to indicate that progress cannot be indicated, and that the task cannot be interrupted.
+ /// Use this, as opposed to `halted(…)`, if a non-interruptable call is about to be made without support
+ /// for any progress indication.
+ ///
+ /// If `eta` is `Some(…)`, it specifies the time at which this task is expected to
+ /// make progress again.
+ ///
+ /// The halted-state is undone next time [`tree::Item::running(…)`][Item::running()] is called.
+ pub fn blocked(&mut self, reason: &'static str, eta: Option<SystemTime>) {
+ self.alter_progress(|p| p.state = State::Blocked(reason, eta));
+ }
+
+ /// Call to indicate that progress cannot be indicated, even though the task can be interrupted.
+ /// Use this, as opposed to `blocked(…)`, if an interruptable call is about to be made without support
+ /// for any progress indication.
+ ///
+ /// If `eta` is `Some(…)`, it specifies the time at which this task is expected to
+ /// make progress again.
+ ///
+ /// The halted-state is undone next time [`tree::Item::running(…)`][Item::running()] is called.
+ pub fn halted(&mut self, reason: &'static str, eta: Option<SystemTime>) {
+ self.alter_progress(|p| p.state = State::Halted(reason, eta));
+ }
+
+ /// Call to indicate that progress is back in running state, which should be called after the reason for
+ /// calling `blocked()` or `halted()` has passed.
+ pub fn running(&mut self) {
+ self.alter_progress(|p| p.state = State::Running);
+ }
+
+ /// Adds a new child `Tree`, whose parent is this instance, with the given `name`.
+ ///
+ /// **Important**: The depth of the hierarchy is limited to [`tree::Key::max_level`](./struct.Key.html#method.max_level).
+ /// Exceeding the level will be ignored, and new tasks will be added to this instance's
+ /// level instead.
+ pub fn add_child(&mut self, name: impl Into<String>) -> Item {
+ self.add_child_with_id(name, crate::progress::UNKNOWN)
+ }
+
+ /// Adds a new child `Tree`, whose parent is this instance, with the given `name` and `id`.
+ ///
+ /// **Important**: The depth of the hierarchy is limited to [`tree::Key::max_level`](./struct.Key.html#method.max_level).
+ /// Exceeding the level will be ignored, and new tasks will be added to this instance's
+ /// level instead.
+ pub fn add_child_with_id(&mut self, name: impl Into<String>, id: Id) -> Item {
+ let child_key = self.key.add_child(self.highest_child_id);
+ let task = Task {
+ name: name.into(),
+ id,
+ progress: None,
+ };
+ #[cfg(feature = "progress-tree-hp-hashmap")]
+ self.tree.insert(child_key, task);
+ #[cfg(not(feature = "progress-tree-hp-hashmap"))]
+ self.tree.insert(child_key, task);
+ self.highest_child_id = self.highest_child_id.wrapping_add(1);
+ Item {
+ highest_child_id: 0,
+ value: Default::default(),
+ key: child_key,
+ tree: Arc::clone(&self.tree),
+ messages: Arc::clone(&self.messages),
+ }
+ }
+
+ /// Create a `message` of the given `level` and store it with the progress tree.
+ ///
+ /// Use this to provide additional,human-readable information about the progress
+ /// made, including indicating success or failure.
+ pub fn message(&mut self, level: MessageLevel, message: impl Into<String>) {
+ let message: String = message.into();
+ self.messages.lock().push_overwrite(
+ level,
+ {
+ let name;
+ #[cfg(feature = "progress-tree-hp-hashmap")]
+ {
+ name = self.tree.get(&self.key).map(|v| v.name.to_owned()).unwrap_or_default();
+ }
+ #[cfg(not(feature = "progress-tree-hp-hashmap"))]
+ {
+ name = self.tree.get(&self.key, |v| v.name.to_owned()).unwrap_or_default()
+ }
+
+ #[cfg(feature = "progress-tree-log")]
+ match level {
+ MessageLevel::Failure => crate::warn!("{} → {}", name, message),
+ MessageLevel::Info | MessageLevel::Success => crate::info!("{} → {}", name, message),
+ };
+
+ name
+ },
+ message,
+ )
+ }
+
+ /// Create a message indicating the task is done
+ pub fn done(&mut self, message: impl Into<String>) {
+ self.message(MessageLevel::Success, message)
+ }
+
+ /// Create a message indicating the task failed
+ pub fn fail(&mut self, message: impl Into<String>) {
+ self.message(MessageLevel::Failure, message)
+ }
+
+ /// Create a message providing additional information about the progress thus far.
+ pub fn info(&mut self, message: impl Into<String>) {
+ self.message(MessageLevel::Info, message)
+ }
+
+ pub(crate) fn deep_clone(&self) -> Item {
+ Item {
+ key: self.key,
+ value: Arc::new(AtomicUsize::new(self.value.load(Ordering::SeqCst))),
+ highest_child_id: self.highest_child_id,
+ tree: Arc::new(self.tree.deref().clone()),
+ messages: Arc::new(Mutex::new(self.messages.lock().clone())),
+ }
+ }
+}
+
+impl crate::Progress for Item {
+ type SubProgress = Item;
+
+ fn add_child(&mut self, name: impl Into<String>) -> Self::SubProgress {
+ Item::add_child(self, name)
+ }
+
+ fn add_child_with_id(&mut self, name: impl Into<String>, id: Id) -> Self::SubProgress {
+ Item::add_child_with_id(self, name, id)
+ }
+
+ fn init(&mut self, max: Option<Step>, unit: Option<Unit>) {
+ Item::init(self, max, unit)
+ }
+
+ fn set(&mut self, step: usize) {
+ Item::set(self, step)
+ }
+
+ fn unit(&self) -> Option<Unit> {
+ Item::unit(self)
+ }
+
+ fn max(&self) -> Option<usize> {
+ Item::max(self)
+ }
+
+ fn set_max(&mut self, max: Option<Step>) -> Option<Step> {
+ Item::set_max(self, max)
+ }
+
+ fn step(&self) -> usize {
+ Item::step(self).unwrap_or(0)
+ }
+
+ fn inc_by(&mut self, step: usize) {
+ self.inc_by(step)
+ }
+
+ fn set_name(&mut self, name: impl Into<String>) {
+ Item::set_name(self, name)
+ }
+
+ fn name(&self) -> Option<String> {
+ Item::name(self)
+ }
+
+ fn id(&self) -> Id {
+ Item::id(self)
+ }
+
+ fn message(&mut self, level: MessageLevel, message: impl Into<String>) {
+ Item::message(self, level, message)
+ }
+
+ fn counter(&self) -> Option<StepShared> {
+ Some(Arc::clone(&self.value))
+ }
+}
diff --git a/vendor/prodash/src/tree/mod.rs b/vendor/prodash/src/tree/mod.rs
new file mode 100644
index 000000000..bbc846d3e
--- /dev/null
+++ b/vendor/prodash/src/tree/mod.rs
@@ -0,0 +1,89 @@
+use crate::messages::MessageRingBuffer;
+
+/// The top-level of the progress tree.
+#[derive(Debug)]
+pub struct Root {
+ pub(crate) inner: parking_lot::Mutex<Item>,
+}
+
+/// A `Tree` represents an element of the progress tree.
+///
+/// It can be used to set progress and send messages.
+/// ```rust
+/// let tree = prodash::tree::Root::new();
+/// let mut progress = tree.add_child("task 1");
+///
+/// progress.init(Some(10), Some("elements".into()));
+/// for p in 0..10 {
+/// progress.set(p);
+/// }
+/// progress.done("great success");
+/// let mut sub_progress = progress.add_child_with_id("sub-task 1", *b"TSK2");
+/// sub_progress.init(None, None);
+/// sub_progress.set(5);
+/// sub_progress.fail("couldn't finish");
+/// ```
+pub struct Item {
+ pub(crate) key: crate::progress::Key,
+ pub(crate) value: crate::progress::StepShared,
+ pub(crate) highest_child_id: crate::progress::key::Id,
+ pub(crate) tree: std::sync::Arc<HashMap<crate::progress::Key, crate::progress::Task>>,
+ pub(crate) messages: std::sync::Arc<parking_lot::Mutex<MessageRingBuffer>>,
+}
+
+#[cfg(feature = "dashmap")]
+type HashMap<K, V> = dashmap::DashMap<K, V>;
+
+#[cfg(not(feature = "dashmap"))]
+type HashMap<K, V> = sync::HashMap<K, V>;
+
+#[cfg(not(feature = "dashmap"))]
+pub(crate) mod sync {
+ pub struct HashMap<K, V>(parking_lot::Mutex<std::collections::HashMap<K, V>>);
+
+ impl<K, V> HashMap<K, V>
+ where
+ K: Eq + std::hash::Hash,
+ {
+ pub fn with_capacity(cap: usize) -> Self {
+ HashMap(parking_lot::Mutex::new(std::collections::HashMap::with_capacity(cap)))
+ }
+ pub fn extend_to(&self, out: &mut Vec<(K, V)>)
+ where
+ K: Clone,
+ V: Clone,
+ {
+ let lock = self.0.lock();
+ out.extend(lock.iter().map(|(k, v)| (k.clone(), v.clone())))
+ }
+ pub fn remove(&self, key: &K) -> Option<V> {
+ self.0.lock().remove(key)
+ }
+ pub fn get<T>(&self, key: &K, cb: impl FnOnce(&V) -> T) -> Option<T> {
+ self.0.lock().get(key).map(cb)
+ }
+ pub fn get_mut<T>(&self, key: &K, cb: impl FnOnce(&mut V) -> T) -> Option<T> {
+ self.0.lock().get_mut(key).map(cb)
+ }
+ pub fn insert(&self, key: K, value: V) {
+ self.0.lock().insert(key, value);
+ }
+ pub fn len(&self) -> usize {
+ self.0.lock().len()
+ }
+ pub fn clone(&self) -> Self
+ where
+ K: Clone,
+ V: Clone,
+ {
+ HashMap(parking_lot::Mutex::new(self.0.lock().clone()))
+ }
+ }
+}
+
+mod item;
+///
+pub mod root;
+
+#[cfg(test)]
+mod tests;
diff --git a/vendor/prodash/src/tree/root.rs b/vendor/prodash/src/tree/root.rs
new file mode 100644
index 000000000..4ff2a9978
--- /dev/null
+++ b/vendor/prodash/src/tree/root.rs
@@ -0,0 +1,179 @@
+use std::{
+ ops::Deref,
+ sync::{atomic::AtomicUsize, Arc, Weak},
+};
+
+use parking_lot::Mutex;
+
+use crate::{
+ messages::{Message, MessageCopyState, MessageRingBuffer},
+ progress::{Id, Key, Task},
+ tree::{Item, Root},
+};
+
+impl Root {
+ /// Create a new tree with default configuration.
+ ///
+ /// As opposed to [Item](./struct.Item.html) instances, this type can be closed and sent
+ /// safely across threads.
+ pub fn new() -> Arc<Root> {
+ Options::default().into()
+ }
+
+ /// Returns the maximum amount of messages we can keep before overwriting older ones.
+ pub fn messages_capacity(&self) -> usize {
+ self.inner.lock().messages.lock().buf.capacity()
+ }
+
+ /// Returns the current amount of `Item`s stored in the tree.
+ /// **Note** that this is at most a guess as tasks can be added and removed in parallel.
+ pub fn num_tasks(&self) -> usize {
+ #[cfg(feature = "progress-tree-hp-hashmap")]
+ {
+ self.inner.lock().tree.len()
+ }
+ #[cfg(not(feature = "progress-tree-hp-hashmap"))]
+ {
+ self.inner.lock().tree.len()
+ }
+ }
+
+ /// Adds a new child `tree::Item`, whose parent is this instance, with the given `name`.
+ ///
+ /// This builds a hierarchy of `tree::Item`s, each having their own progress.
+ /// Use this method to [track progress](./struct.Item.html) of your first tasks.
+ pub fn add_child(&self, name: impl Into<String>) -> Item {
+ self.inner.lock().add_child(name)
+ }
+
+ /// Adds a new child `tree::Item`, whose parent is this instance, with the given `name` and `id`.
+ ///
+ /// This builds a hierarchy of `tree::Item`s, each having their own progress.
+ /// Use this method to [track progress](./struct.Item.html) of your first tasks.
+ pub fn add_child_with_id(&self, name: impl Into<String>, id: Id) -> Item {
+ self.inner.lock().add_child_with_id(name, id)
+ }
+
+ /// Copy the entire progress tree into the given `out` vector, so that
+ /// it can be traversed from beginning to end in order of hierarchy.
+ pub fn sorted_snapshot(&self, out: &mut Vec<(Key, Task)>) {
+ out.clear();
+ #[cfg(feature = "progress-tree-hp-hashmap")]
+ out.extend(self.inner.lock().tree.iter().map(|r| (*r.key(), r.value().clone())));
+ #[cfg(not(feature = "progress-tree-hp-hashmap"))]
+ self.inner.lock().tree.extend_to(out);
+ out.sort_by_key(|t| t.0);
+ }
+
+ /// Copy all messages from the internal ring buffer into the given `out`
+ /// vector. Messages are ordered from oldest to newest.
+ pub fn copy_messages(&self, out: &mut Vec<Message>) {
+ self.inner.lock().messages.lock().copy_all(out);
+ }
+
+ /// Copy only new messages from the internal ring buffer into the given `out`
+ /// vector. Messages are ordered from oldest to newest.
+ pub fn copy_new_messages(&self, out: &mut Vec<Message>, prev: Option<MessageCopyState>) -> MessageCopyState {
+ self.inner.lock().messages.lock().copy_new(out, prev)
+ }
+
+ /// Duplicate all content and return it.
+ ///
+ /// This is an expensive operation, whereas `clone()` is not as it is shallow.
+ pub fn deep_clone(&self) -> Arc<Root> {
+ Arc::new(Root {
+ inner: Mutex::new(self.inner.lock().deep_clone()),
+ })
+ }
+}
+
+/// A way to configure new [`tree::Root`](./tree/struct.Root.html) instances
+/// ```rust
+/// let tree = prodash::tree::root::Options::default().create();
+/// let tree2 = prodash::tree::root::Options { message_buffer_capacity: 100, ..Default::default() }.create();
+/// ```
+#[derive(Clone, Debug)]
+pub struct Options {
+ /// The amount of [items][Item] the tree can hold without being forced to allocate.
+ pub initial_capacity: usize,
+ /// The amount of messages we can hold before we start overwriting old ones.
+ pub message_buffer_capacity: usize,
+}
+
+impl Options {
+ /// Create a new [`Root`](./tree/struct.Root.html) instance from the
+ /// configuration within.
+ pub fn create(self) -> Root {
+ self.into()
+ }
+}
+
+impl Default for Options {
+ fn default() -> Self {
+ Options {
+ initial_capacity: 100,
+ message_buffer_capacity: 20,
+ }
+ }
+}
+
+impl From<Options> for Arc<Root> {
+ fn from(opts: Options) -> Self {
+ Arc::new(opts.into())
+ }
+}
+
+impl From<Options> for Root {
+ fn from(
+ Options {
+ initial_capacity,
+ message_buffer_capacity,
+ }: Options,
+ ) -> Self {
+ Root {
+ inner: Mutex::new(Item {
+ highest_child_id: 0,
+ value: Arc::new(AtomicUsize::default()),
+ key: Key::default(),
+ tree: Arc::new(crate::tree::HashMap::with_capacity(initial_capacity)),
+ messages: Arc::new(Mutex::new(MessageRingBuffer::with_capacity(message_buffer_capacity))),
+ }),
+ }
+ }
+}
+
+impl crate::WeakRoot for Weak<Root> {
+ type Root = Arc<Root>;
+
+ fn upgrade(&self) -> Option<Self::Root> {
+ Weak::upgrade(self)
+ }
+}
+
+impl crate::Root for Arc<Root> {
+ type WeakRoot = Weak<Root>;
+
+ fn messages_capacity(&self) -> usize {
+ self.deref().messages_capacity()
+ }
+
+ fn num_tasks(&self) -> usize {
+ self.deref().num_tasks()
+ }
+
+ fn sorted_snapshot(&self, out: &mut Vec<(Key, Task)>) {
+ self.deref().sorted_snapshot(out)
+ }
+
+ fn copy_messages(&self, out: &mut Vec<Message>) {
+ self.deref().copy_messages(out)
+ }
+
+ fn copy_new_messages(&self, out: &mut Vec<Message>, prev: Option<MessageCopyState>) -> MessageCopyState {
+ self.deref().copy_new_messages(out, prev)
+ }
+
+ fn downgrade(&self) -> Self::WeakRoot {
+ Arc::downgrade(self)
+ }
+}
diff --git a/vendor/prodash/src/tree/tests.rs b/vendor/prodash/src/tree/tests.rs
new file mode 100644
index 000000000..03a103df9
--- /dev/null
+++ b/vendor/prodash/src/tree/tests.rs
@@ -0,0 +1,115 @@
+mod message_buffer {
+ use crate::messages::{Message, MessageLevel, MessageRingBuffer};
+
+ fn push(buf: &mut MessageRingBuffer, msg: impl Into<String>) {
+ buf.push_overwrite(MessageLevel::Info, "test".into(), msg);
+ }
+ fn push_and_copy_all(buf: &mut MessageRingBuffer, msg: impl Into<String>, out: &mut Vec<Message>) {
+ push(buf, msg);
+ buf.copy_all(out);
+ }
+
+ fn assert_messages(actual: &[Message], expected: &[&'static str]) {
+ let actual: Vec<_> = actual.iter().map(|m| m.message.as_str()).collect();
+ assert_eq!(expected, actual.as_slice(), "messages are ordered old to new");
+ }
+
+ #[test]
+ fn copy_all() {
+ let mut buf = MessageRingBuffer::with_capacity(2);
+ let mut out = Vec::new();
+ buf.copy_all(&mut out);
+ assert_eq!(out, buf.buf);
+
+ push_and_copy_all(&mut buf, "one", &mut out);
+ assert_eq!(out, buf.buf);
+
+ push_and_copy_all(&mut buf, "two", &mut out);
+ assert_eq!(out, buf.buf);
+
+ push_and_copy_all(&mut buf, "three", &mut out);
+ assert_messages(&out, &["two", "three"]);
+
+ push_and_copy_all(&mut buf, "four", &mut out);
+ assert_messages(&out, &["three", "four"]);
+
+ push_and_copy_all(&mut buf, "five", &mut out);
+ buf.copy_all(&mut out);
+ assert_messages(&out, &["four", "five"]);
+ }
+
+ mod copy_new {
+ use crate::{
+ messages::{Message, MessageCopyState, MessageRingBuffer},
+ tree::tests::message_buffer::{assert_messages, push},
+ };
+
+ #[test]
+ fn without_state() {
+ fn push_and_copy_new(buf: &mut MessageRingBuffer, msg: impl Into<String>, out: &mut Vec<Message>) {
+ push(buf, msg);
+ buf.copy_new(out, None);
+ }
+
+ let mut buf = MessageRingBuffer::with_capacity(2);
+ let mut out = Vec::new();
+ buf.copy_new(&mut out, None);
+ assert_eq!(out, buf.buf);
+
+ push_and_copy_new(&mut buf, "one", &mut out);
+ assert_eq!(out, buf.buf);
+
+ push_and_copy_new(&mut buf, "two", &mut out);
+ assert_eq!(out, buf.buf);
+
+ push_and_copy_new(&mut buf, "three", &mut out);
+ assert_messages(&out, &["two", "three"]);
+ }
+
+ #[test]
+ fn with_continous_state() {
+ fn push_and_copy_new(
+ buf: &mut MessageRingBuffer,
+ msg: impl Into<String>,
+ out: &mut Vec<Message>,
+ state: Option<MessageCopyState>,
+ ) -> Option<MessageCopyState> {
+ push(buf, msg);
+ Some(buf.copy_new(out, state))
+ }
+ let mut buf = MessageRingBuffer::with_capacity(2);
+ let mut out = Vec::new();
+ let mut state = push_and_copy_new(&mut buf, "one", &mut out, None);
+ assert_eq!(out, buf.buf);
+
+ state = push_and_copy_new(&mut buf, "two", &mut out, state);
+ assert_messages(&out, &["two"]);
+
+ state = push_and_copy_new(&mut buf, "three", &mut out, state);
+ assert_messages(&out, &["three"]);
+
+ state = push_and_copy_new(&mut buf, "four", &mut out, state);
+ assert_messages(&out, &["four"]);
+
+ push_and_copy_new(&mut buf, "five", &mut out, state);
+ assert_messages(&out, &["five"]);
+
+ state = push_and_copy_new(&mut buf, "six", &mut out, None);
+ assert_messages(&out, &["five", "six"]);
+
+ state = Some(buf.copy_new(&mut out, state));
+ assert_messages(&out, &[]);
+
+ push(&mut buf, "seven");
+ push(&mut buf, "eight");
+ state = Some(buf.copy_new(&mut out, state));
+ assert_messages(&out, &["seven", "eight"]);
+
+ push(&mut buf, "1");
+ push(&mut buf, "2");
+ push(&mut buf, "3");
+ buf.copy_new(&mut out, state);
+ assert_messages(&out, &["2", "3"]);
+ }
+ }
+}
diff --git a/vendor/prodash/src/unit/bytes.rs b/vendor/prodash/src/unit/bytes.rs
new file mode 100644
index 000000000..a79f97195
--- /dev/null
+++ b/vendor/prodash/src/unit/bytes.rs
@@ -0,0 +1,34 @@
+use std::fmt;
+
+use crate::{progress::Step, unit::DisplayValue};
+
+/// A marker for formatting numbers as bytes in renderers.
+#[derive(Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd, Debug)]
+pub struct Bytes;
+
+impl Bytes {
+ fn format_bytes(w: &mut dyn fmt::Write, value: Step) -> fmt::Result {
+ let string = bytesize::to_string(value as u64, false);
+ for token in string.split(' ') {
+ w.write_str(token)?;
+ }
+ Ok(())
+ }
+}
+
+impl DisplayValue for Bytes {
+ fn display_current_value(&self, w: &mut dyn fmt::Write, value: Step, _upper: Option<Step>) -> fmt::Result {
+ Self::format_bytes(w, value)
+ }
+ fn display_upper_bound(&self, w: &mut dyn fmt::Write, upper_bound: Step, _value: Step) -> fmt::Result {
+ Self::format_bytes(w, upper_bound)
+ }
+
+ fn dyn_hash(&self, state: &mut dyn std::hash::Hasher) {
+ state.write(&[])
+ }
+
+ fn display_unit(&self, _w: &mut dyn fmt::Write, _value: Step) -> fmt::Result {
+ Ok(())
+ }
+}
diff --git a/vendor/prodash/src/unit/display.rs b/vendor/prodash/src/unit/display.rs
new file mode 100644
index 000000000..592692ff5
--- /dev/null
+++ b/vendor/prodash/src/unit/display.rs
@@ -0,0 +1,188 @@
+use std::fmt::{self, Write};
+
+use crate::{
+ progress::Step,
+ unit::{DisplayValue, Unit},
+};
+
+/// The location at which [`Throughput`] or [`UnitDisplays`][UnitDisplay] should be placed.
+#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)]
+#[allow(missing_docs)]
+pub enum Location {
+ BeforeValue,
+ AfterUnit,
+}
+
+/// A structure able to display throughput, a value change within a given duration.
+#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
+pub struct Throughput {
+ /// The change of value between the current value and the previous one.
+ pub value_change_in_timespan: Step,
+ /// The amount of time passed between the previous and the current value.
+ pub timespan: std::time::Duration,
+}
+
+impl Throughput {
+ /// A convenience method to create a new ThroughPut from `value_change_in_timespan` and `timespan`.
+ pub fn new(value_change_in_timespan: Step, timespan: std::time::Duration) -> Self {
+ Throughput {
+ value_change_in_timespan,
+ timespan,
+ }
+ }
+}
+
+/// A way to display a [Unit].
+#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)]
+pub struct Mode {
+ location: Location,
+ percent: bool,
+ throughput: bool,
+}
+
+impl Mode {
+ fn percent_location(&self) -> Option<Location> {
+ if self.percent {
+ Some(self.location)
+ } else {
+ None
+ }
+ }
+
+ fn throughput_location(&self) -> Option<Location> {
+ if self.throughput {
+ Some(self.location)
+ } else {
+ None
+ }
+ }
+}
+
+/// initialization and modification
+impl Mode {
+ /// Create a mode instance with percentage only.
+ pub fn with_percentage() -> Self {
+ Mode {
+ percent: true,
+ throughput: false,
+ location: Location::AfterUnit,
+ }
+ }
+ /// Create a mode instance with throughput only.
+ pub fn with_throughput() -> Self {
+ Mode {
+ percent: false,
+ throughput: true,
+ location: Location::AfterUnit,
+ }
+ }
+ /// Turn on percentage display on the current instance.
+ pub fn and_percentage(mut self) -> Self {
+ self.percent = true;
+ self
+ }
+ /// Turn on throughput display on the current instance.
+ pub fn and_throughput(mut self) -> Self {
+ self.throughput = true;
+ self
+ }
+ /// Change the display location to show up in front of the value.
+ pub fn show_before_value(mut self) -> Self {
+ self.location = Location::BeforeValue;
+ self
+ }
+}
+
+/// A utility to implement [Display][std::fmt::Display].
+pub struct UnitDisplay<'a> {
+ pub(crate) current_value: Step,
+ pub(crate) upper_bound: Option<Step>,
+ pub(crate) throughput: Option<Throughput>,
+ pub(crate) parent: &'a Unit,
+ pub(crate) display: What,
+}
+
+pub(crate) enum What {
+ ValuesAndUnit,
+ Unit,
+ Values,
+}
+
+impl What {
+ fn values(&self) -> bool {
+ matches!(self, What::Values | What::ValuesAndUnit)
+ }
+ fn unit(&self) -> bool {
+ matches!(self, What::Unit | What::ValuesAndUnit)
+ }
+}
+
+impl<'a> UnitDisplay<'a> {
+ /// Display everything, values and the unit.
+ pub fn all(&mut self) -> &Self {
+ self.display = What::ValuesAndUnit;
+ self
+ }
+ /// Display only values.
+ pub fn values(&mut self) -> &Self {
+ self.display = What::Values;
+ self
+ }
+ /// Display only units.
+ pub fn unit(&mut self) -> &Self {
+ self.display = What::Unit;
+ self
+ }
+}
+
+impl<'a> fmt::Display for UnitDisplay<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let unit: &dyn DisplayValue = self.parent.as_display_value();
+ let mode = self.parent.mode;
+
+ let percent_location_and_fraction = self.upper_bound.and_then(|upper| {
+ mode.and_then(|m| m.percent_location())
+ .map(|location| (location, ((self.current_value as f64 / upper as f64) * 100.0).floor()))
+ });
+ let throughput_and_location = self.throughput.as_ref().and_then(|throughput| {
+ mode.and_then(|m| m.throughput_location())
+ .map(|location| (location, throughput))
+ });
+ if self.display.values() {
+ if let Some((Location::BeforeValue, fraction)) = percent_location_and_fraction {
+ unit.display_percentage(f, fraction)?;
+ f.write_char(' ')?;
+ }
+ if let Some((Location::BeforeValue, throughput)) = throughput_and_location {
+ unit.display_throughput(f, throughput)?;
+ f.write_char(' ')?;
+ }
+ unit.display_current_value(f, self.current_value, self.upper_bound)?;
+ if let Some(upper) = self.upper_bound {
+ unit.separator(f, self.current_value, self.upper_bound)?;
+ unit.display_upper_bound(f, upper, self.current_value)?;
+ }
+ }
+ if self.display.unit() {
+ let mut buf = String::with_capacity(10);
+ if self.display.values() {
+ buf.write_char(' ')?;
+ }
+ unit.display_unit(&mut buf, self.current_value)?;
+ if buf.len() > 1 {
+ // did they actually write a unit?
+ f.write_str(&buf)?;
+ }
+
+ if let Some((Location::AfterUnit, fraction)) = percent_location_and_fraction {
+ f.write_char(' ')?;
+ unit.display_percentage(f, fraction)?;
+ }
+ if let Some((Location::AfterUnit, throughput)) = throughput_and_location {
+ f.write_char(' ')?;
+ unit.display_throughput(f, throughput)?;
+ }
+ }
+ Ok(())
+ }
+}
diff --git a/vendor/prodash/src/unit/duration.rs b/vendor/prodash/src/unit/duration.rs
new file mode 100644
index 000000000..0da23e89b
--- /dev/null
+++ b/vendor/prodash/src/unit/duration.rs
@@ -0,0 +1,27 @@
+use std::fmt;
+
+use crate::{progress::Step, unit::DisplayValue};
+
+/// A marker for formatting numbers as duration in renderers, as in `7d4h20m10s`.
+#[derive(Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd, Debug)]
+pub struct Duration;
+
+impl DisplayValue for Duration {
+ fn display_current_value(&self, w: &mut dyn fmt::Write, value: Step, _upper: Option<Step>) -> fmt::Result {
+ w.write_str(&compound_duration::format_dhms(value))
+ }
+ fn separator(&self, w: &mut dyn fmt::Write, _value: Step, _upper: Option<Step>) -> fmt::Result {
+ w.write_str(" of ")
+ }
+ fn display_upper_bound(&self, w: &mut dyn fmt::Write, upper_bound: Step, _value: Step) -> fmt::Result {
+ w.write_str(&compound_duration::format_dhms(upper_bound))
+ }
+
+ fn dyn_hash(&self, state: &mut dyn std::hash::Hasher) {
+ state.write(&[])
+ }
+
+ fn display_unit(&self, _w: &mut dyn fmt::Write, _value: Step) -> fmt::Result {
+ Ok(())
+ }
+}
diff --git a/vendor/prodash/src/unit/human.rs b/vendor/prodash/src/unit/human.rs
new file mode 100644
index 000000000..4b27f1e36
--- /dev/null
+++ b/vendor/prodash/src/unit/human.rs
@@ -0,0 +1,47 @@
+use std::{fmt, fmt::Debug, hash::Hasher};
+
+pub use human_format::{Formatter, Scales};
+
+use crate::{progress::Step, unit::DisplayValue};
+
+/// A helper for formatting numbers in a format easily read by humans in renderers, as in `2.54 million objects`
+#[derive(Debug)]
+pub struct Human {
+ /// The name of the represented unit, like 'items' or 'objects'.
+ pub name: &'static str,
+ /// The formatter to format the actual numbers.
+ pub formatter: Formatter,
+}
+
+impl Human {
+ /// A convenience method to create a new new instance and its `formatter` and `name` fields.
+ pub fn new(formatter: Formatter, name: &'static str) -> Self {
+ Human { name, formatter }
+ }
+ fn format_bytes(&self, w: &mut dyn fmt::Write, value: Step) -> fmt::Result {
+ let string = self.formatter.format(value as f64);
+ for token in string.split(' ') {
+ w.write_str(token)?;
+ }
+ Ok(())
+ }
+}
+
+impl DisplayValue for Human {
+ fn display_current_value(&self, w: &mut dyn fmt::Write, value: Step, _upper: Option<Step>) -> fmt::Result {
+ self.format_bytes(w, value)
+ }
+
+ fn display_upper_bound(&self, w: &mut dyn fmt::Write, upper_bound: Step, _value: Step) -> fmt::Result {
+ self.format_bytes(w, upper_bound)
+ }
+
+ fn dyn_hash(&self, state: &mut dyn Hasher) {
+ state.write(self.name.as_bytes());
+ state.write_u8(0);
+ }
+
+ fn display_unit(&self, w: &mut dyn fmt::Write, _value: Step) -> fmt::Result {
+ w.write_str(self.name)
+ }
+}
diff --git a/vendor/prodash/src/unit/mod.rs b/vendor/prodash/src/unit/mod.rs
new file mode 100644
index 000000000..3ebfa1c74
--- /dev/null
+++ b/vendor/prodash/src/unit/mod.rs
@@ -0,0 +1,140 @@
+use std::{fmt, ops::Deref, sync::Arc};
+
+use crate::progress::Step;
+
+#[cfg(feature = "unit-bytes")]
+mod bytes;
+#[cfg(feature = "unit-bytes")]
+pub use bytes::Bytes;
+
+#[cfg(feature = "unit-duration")]
+mod duration;
+#[cfg(feature = "unit-duration")]
+pub use duration::Duration;
+
+#[cfg(feature = "unit-human")]
+///
+pub mod human;
+#[cfg(feature = "unit-human")]
+#[doc(inline)]
+pub use human::Human;
+
+mod range;
+pub use range::Range;
+
+mod traits;
+pub use traits::DisplayValue;
+
+/// Various utilities to display values and units.
+pub mod display;
+
+/// A configurable and flexible unit for use in [Progress::init()][crate::Progress::init()].
+#[derive(Debug, Clone, Hash)]
+pub struct Unit {
+ kind: Kind,
+ mode: Option<display::Mode>,
+}
+
+/// Either a static label or a dynamic one implementing [`DisplayValue`].
+#[derive(Clone)]
+pub enum Kind {
+ /// Display only the given statically known label.
+ Label(&'static str),
+ /// Display a label created dynamically.
+ Dynamic(Arc<dyn DisplayValue + Send + Sync>),
+}
+
+impl std::hash::Hash for Kind {
+ fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+ match self {
+ Kind::Label(s) => {
+ 0.hash(state);
+ s.dyn_hash(state)
+ }
+ Kind::Dynamic(label) => {
+ 1.hash(state);
+ label.dyn_hash(state);
+ }
+ }
+ }
+}
+
+impl fmt::Debug for Kind {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Kind::Label(name) => f.write_fmt(format_args!("Unit::Label({:?})", name)),
+ Kind::Dynamic(_) => f.write_fmt(format_args!("Unit::Dynamic(..)")),
+ }
+ }
+}
+
+impl From<&'static str> for Unit {
+ fn from(v: &'static str) -> Self {
+ label(v)
+ }
+}
+
+/// Returns a unit that is a static `label`.
+pub fn label(label: &'static str) -> Unit {
+ Unit {
+ kind: Kind::Label(label),
+ mode: None,
+ }
+}
+
+/// Returns a unit that is a static `label` along with information on where to display a fraction and throughput.
+pub fn label_and_mode(label: &'static str, mode: display::Mode) -> Unit {
+ Unit {
+ kind: Kind::Label(label),
+ mode: Some(mode),
+ }
+}
+
+/// Returns a unit that is a dynamic `label`.
+pub fn dynamic(label: impl DisplayValue + Send + Sync + 'static) -> Unit {
+ Unit {
+ kind: Kind::Dynamic(Arc::new(label)),
+ mode: None,
+ }
+}
+
+/// Returns a unit that is a dynamic `label` along with information on where to display a fraction and throughput.
+pub fn dynamic_and_mode(label: impl DisplayValue + Send + Sync + 'static, mode: display::Mode) -> Unit {
+ Unit {
+ kind: Kind::Dynamic(Arc::new(label)),
+ mode: Some(mode),
+ }
+}
+
+/// Display and utilities
+impl Unit {
+ /// Create a representation of `self` implementing [`Display`][std::fmt::Display] in configurable fashion.
+ ///
+ /// * `current_value` is the progress value to display.
+ /// * `upper_bound` is the possibly available upper bound of `current_value`.
+ /// * `throughput` configures how throughput should be displayed if already available.
+ ///
+ /// Note that `throughput` is usually not available the first time a value is displayed.
+ pub fn display(
+ &self,
+ current_value: Step,
+ upper_bound: Option<Step>,
+ throughput: impl Into<Option<display::Throughput>>,
+ ) -> display::UnitDisplay {
+ display::UnitDisplay {
+ current_value,
+ upper_bound,
+ throughput: throughput.into(),
+ parent: self,
+ display: display::What::ValuesAndUnit,
+ }
+ }
+
+ /// Return `self` as trait object implementing `DisplayValue`.
+ pub fn as_display_value(&self) -> &dyn DisplayValue {
+ match self.kind {
+ Kind::Label(ref unit) => unit,
+ Kind::Dynamic(ref unit) => unit.deref(),
+ }
+ }
+}
diff --git a/vendor/prodash/src/unit/range.rs b/vendor/prodash/src/unit/range.rs
new file mode 100644
index 000000000..4f455bf96
--- /dev/null
+++ b/vendor/prodash/src/unit/range.rs
@@ -0,0 +1,34 @@
+use std::{fmt, hash::Hasher};
+
+use crate::{progress::Step, unit::DisplayValue};
+
+/// A helper for formatting numbers representing ranges in renderers as in `2 of 5 steps`.
+#[derive(Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd, Debug)]
+pub struct Range {
+ /// The name of the unit to be appended to the range.
+ pub name: &'static str,
+}
+
+impl Range {
+ /// A convenience method to create a new instance of `name`.
+ pub fn new(name: &'static str) -> Self {
+ Range { name }
+ }
+}
+
+impl DisplayValue for Range {
+ fn display_current_value(&self, w: &mut dyn fmt::Write, value: Step, _upper: Option<Step>) -> fmt::Result {
+ w.write_fmt(format_args!("{}", value + 1))
+ }
+ fn separator(&self, w: &mut dyn fmt::Write, _value: Step, _upper: Option<Step>) -> fmt::Result {
+ w.write_str(" of ")
+ }
+
+ fn dyn_hash(&self, state: &mut dyn Hasher) {
+ self.name.dyn_hash(state)
+ }
+
+ fn display_unit(&self, w: &mut dyn fmt::Write, _value: Step) -> fmt::Result {
+ w.write_str(self.name)
+ }
+}
diff --git a/vendor/prodash/src/unit/traits.rs b/vendor/prodash/src/unit/traits.rs
new file mode 100644
index 000000000..33b529e4a
--- /dev/null
+++ b/vendor/prodash/src/unit/traits.rs
@@ -0,0 +1,93 @@
+use std::{fmt, hash::Hasher};
+
+use crate::{progress::Step, unit::display};
+
+/// A trait to encapsulate all capabilities needed to display a value with unit within a renderer.
+pub trait DisplayValue {
+ /// Display the absolute `value` representing the current progress of an operation and write it to `w`.
+ ///
+ /// The `upper` bound is possibly provided when known to add context, even though it is not to be output
+ /// as part of this method call.
+ fn display_current_value(&self, w: &mut dyn fmt::Write, value: Step, _upper: Option<Step>) -> fmt::Result {
+ fmt::write(w, format_args!("{}", value))
+ }
+ /// Emit a token to separate two values.
+ ///
+ /// The `value` and its `upper` bound are provided to add context, even though it is not to be output
+ /// as part of this method call.
+ fn separator(&self, w: &mut dyn fmt::Write, _value: Step, _upper: Option<Step>) -> fmt::Result {
+ w.write_str("/")
+ }
+
+ /// Emit the `upper_bound` to `w`.
+ ///
+ /// The `value` is provided to add context, even though it is not to be output as part of this method call.
+ fn display_upper_bound(&self, w: &mut dyn fmt::Write, upper_bound: Step, _value: Step) -> fmt::Result {
+ fmt::write(w, format_args!("{}", upper_bound))
+ }
+
+ /// A way to hash our state without using generics.
+ ///
+ /// This helps to determine quickly if something changed.
+ fn dyn_hash(&self, state: &mut dyn std::hash::Hasher);
+
+ /// Emit the unit of `value` to `w`.
+ ///
+ /// The `value` is provided to add context, even though it is not to be output as part of this method call.
+ fn display_unit(&self, w: &mut dyn fmt::Write, value: Step) -> fmt::Result;
+
+ /// Emit `percentage` to `w`.
+ fn display_percentage(&self, w: &mut dyn fmt::Write, percentage: f64) -> fmt::Result {
+ w.write_fmt(format_args!("[{}%]", percentage as usize))
+ }
+
+ /// Emit the `throughput` of an operation to `w`.
+ fn display_throughput(&self, w: &mut dyn fmt::Write, throughput: &display::Throughput) -> fmt::Result {
+ let (fraction, unit) = self.fraction_and_time_unit(throughput.timespan);
+ w.write_char('|')?;
+ self.display_current_value(w, throughput.value_change_in_timespan, None)?;
+ w.write_char('/')?;
+ match fraction {
+ Some(fraction) => w.write_fmt(format_args!("{}", fraction)),
+ None => Ok(()),
+ }?;
+ w.write_fmt(format_args!("{}|", unit))
+ }
+
+ /// Given a `timespan`, return a fraction of the timespan based on the given unit, i.e. `(possible fraction, unit`).
+ fn fraction_and_time_unit(&self, timespan: std::time::Duration) -> (Option<f64>, &'static str) {
+ fn skip_one(v: f64) -> Option<f64> {
+ if (v - 1.0).abs() < f64::EPSILON {
+ None
+ } else {
+ Some(v)
+ }
+ }
+ const HOUR_IN_SECS: u64 = 60 * 60;
+ let secs = timespan.as_secs();
+ let h = secs / HOUR_IN_SECS;
+ if h > 0 {
+ return (skip_one(secs as f64 / HOUR_IN_SECS as f64), "h");
+ }
+ const MINUTES_IN_SECS: u64 = 60;
+ let m = secs / MINUTES_IN_SECS;
+ if m > 0 {
+ return (skip_one(secs as f64 / MINUTES_IN_SECS as f64), "m");
+ }
+ if secs > 0 {
+ return (skip_one(secs as f64), "s");
+ }
+
+ (skip_one(timespan.as_millis() as f64), "ms")
+ }
+}
+
+impl DisplayValue for &'static str {
+ fn dyn_hash(&self, state: &mut dyn Hasher) {
+ state.write(self.as_bytes())
+ }
+
+ fn display_unit(&self, w: &mut dyn fmt::Write, _value: usize) -> fmt::Result {
+ w.write_fmt(format_args!("{}", self))
+ }
+}