[
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n- package-ecosystem: cargo\n  directory: '/'\n  schedule:\n    interval: monthly\n  commit-message:\n    prefix: ''\n  allow:\n  - dependency-type: all\n  groups:\n    cargo:\n      patterns: ['*']\n  cooldown:\n    default-days: 7\n\n- package-ecosystem: github-actions\n  directory: '/'\n  schedule:\n    interval: monthly\n  commit-message:\n    prefix: ''\n  groups:\n    github-actions:\n      patterns: ['*']\n  cooldown:\n    default-days: 7\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    branches:\n    - main\n    - \"run-ci/**\"\n    - \"**/run-ci/**\"\n  pull_request:\n    branches:\n    - main\n  workflow_dispatch:\n\npermissions:\n  contents: read\n\njobs:\n  build-and-test-linux:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      with:\n        persist-credentials: false\n    - name: Install Rust\n      run: |\n        rustup update stable\n        rustup default stable\n        rustup component add clippy rustfmt\n    - name: fmt\n      run: cargo fmt --all -- --check\n    - name: clippy\n      run: cargo clippy\n    - name: tests\n      run: make tests\n    - name: \"Test (crossterm)\"\n      run: |\n        cargo test --features=render-tui,render-tui-crossterm,render-line,render-line-crossterm,signal-hook,render-line-autoconfigure,progress-tree --all --bins --tests --examples\n    - name: benchmarks\n      run: make bench-ci\n\n  build-and-test-on-windows:\n    runs-on: windows-latest\n    steps:\n    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      with:\n        persist-credentials: false\n    - name: Install Rust\n      shell: bash  # Use `bash` even on Windows, for `set -e` behavior.\n      run: |\n        rustup update stable\n        rustup default stable\n    - name: \"Check (crossterm)\"\n      run: |\n        cargo check --features=render-tui,render-tui-crossterm,render-line,render-line-crossterm,signal-hook,render-line-autoconfigure,progress-tree --all --bins --tests --examples\n    - name: \"Test (crossterm)\"\n      run: |\n        cargo test --features=render-tui,render-tui-crossterm,render-line,render-line-crossterm,signal-hook,render-line-autoconfigure,progress-tree --all -- --skip render::tui\n"
  },
  {
    "path": ".github/workflows/codeql.yml",
    "content": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# You may wish to alter this file to override the set of languages analyzed,\n# or to provide custom queries or build logic.\n#\n# ******** NOTE ********\n# We have attempted to detect the languages in your repository. Please check\n# the `language` matrix defined below to confirm you have the correct set of\n# supported CodeQL languages.\n#\nname: CodeQL\n\non:\n  push:\n    branches:\n    - main\n    - \"run-ci/**\"\n    - \"**/run-ci/**\"\n  pull_request:\n    branches:\n    - main\n  schedule:\n  - cron: '28 17 * * 3'\n  workflow_dispatch:\n\npermissions: {}  # Expanded in the `analyze` job.\n\njobs:\n  analyze:\n    name: Analyze (${{ matrix.language }})\n    # Runner size impacts CodeQL analysis time. To learn more, please see:\n    #   - https://gh.io/recommended-hardware-resources-for-running-codeql\n    #   - https://gh.io/supported-runners-and-hardware-resources\n    #   - https://gh.io/using-larger-runners (GitHub.com only)\n    # Consider using larger runners or machines with greater resources for possible analysis time improvements.\n    runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}\n    permissions:\n      # required for all workflows\n      security-events: write  # For uploading SARIF to view in the Security tab.\n\n      # required to fetch internal or private CodeQL packs\n      # packages: read\n\n      # only required for workflows in private repositories\n      # actions: read\n      # contents: read\n\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n        - language: actions\n          build-mode: none\n        - language: rust\n          build-mode: none\n        # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'\n        # Use `c-cpp` to analyze code written in C, C++ or both\n        # Use 'java-kotlin' to analyze code written in Java, Kotlin or both\n        # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both\n        # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,\n        # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.\n        # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how\n        # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      with:\n        persist-credentials: false\n\n    # Add any setup steps before running the `github/codeql-action/init` action.\n    # This includes steps like installing compilers or runtimes (`actions/setup-node`\n    # or others). This is typically only required for manual builds.\n    # - name: Setup runtime (example)\n    #   uses: actions/setup-example@v1\n\n    # Initializes the CodeQL tools for scanning.\n    - name: Initialize CodeQL\n      uses: github/codeql-action/init@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4\n      with:\n        languages: ${{ matrix.language }}\n        build-mode: ${{ matrix.build-mode }}\n        # If you wish to specify custom queries, you can do so here or in a config file.\n        # By default, queries listed here will override any specified in a config file.\n        # Prefix the list here with \"+\" to use these queries and those in the config file.\n\n        # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs\n        queries: security-extended,security-and-quality\n\n    # If the analyze step fails for one of the languages you are analyzing with\n    # \"We were unable to automatically build your code\", modify the matrix above\n    # to set the build mode to \"manual\" for that language. Then modify this step\n    # to build your code.\n    # ℹ️ Command-line programs to run using the OS shell.\n    # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun\n    - name: Run manual build steps\n      if: matrix.build-mode == 'manual'\n      shell: bash\n      run: |\n        echo 'If you are using a \"manual\" build mode for one or more of the' \\\n          'languages you are analyzing, replace this with the commands to build' \\\n          'your code, for example:'\n        echo '  make bootstrap'\n        echo '  make release'\n        exit 1\n\n    - name: Perform CodeQL Analysis\n      uses: github/codeql-action/analyze@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4\n      with:\n        category: \"/language:${{ matrix.language }}\"\n"
  },
  {
    "path": ".github/workflows/zizmor.yml",
    "content": "name: GitHub Actions Security Analysis with zizmor 🌈\n\non:\n  push:\n    branches:\n    - main\n    - \"run-ci/**\"\n    - \"**/run-ci/**\"\n  pull_request:\n    branches:\n    - main\n  workflow_dispatch:\n\npermissions: {}  # Expanded in the `zizmor` job.\n\njobs:\n  zizmor:\n    runs-on: ubuntu-latest\n    permissions:\n      security-events: write  # Required for uploading SARIF to view in the Security tab.\n      contents: read  # Not needed in public repos. (Kept for private forks/reuploads.)\n      actions: read  # Not needed in public repos. (Kept for private forks/reuploads.)\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      with:\n        persist-credentials: false\n    - name: Run zizmor 🌈\n      uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2\n      with:\n        persona: pedantic\n"
  },
  {
    "path": ".github/zizmor.yml",
    "content": "rules:\n  anonymous-definition:\n    disable: true\n  concurrency-limits:\n    disable: true\n"
  },
  {
    "path": ".gitignore",
    "content": "\n# Created by https://www.gitignore.io/api/rust\n# Edit at https://www.gitignore.io/?templates=rust\n\n### Rust ###\n# Generated by Cargo\n# will have compiled files and executables\n/target/\n\n# These are backup files generated by rustfmt\n**/*.rs.bk\n\n# End of https://www.gitignore.io/api/rust\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## 31.0.0 (2026-01-08)\n\n### Chore\n\n - <csr-id-7d21bb5cff9a7df720e9e56419235e76a9924f0b/> Update various dependnecies\n\n### Bug Fixes\n\n - <csr-id-3df00691101d2d21eeb82df03dbe1ca3bf45263e/> fix a few broken doc links\n\n### Other\n\n - <csr-id-8bdc42571ebdda35a05df55129b0a759698b10f6/> bump actions/checkout in the github-actions group\n   Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout).\n   \n   \n   Updates `actions/checkout` from 5 to 6\n   - [Release notes](https://github.com/actions/checkout/releases)\n   - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)\n   - [Commits](https://github.com/actions/checkout/compare/v5...v6)\n   \n   ---\n   updated-dependencies:\n   - dependency-name: actions/checkout\n     dependency-version: '6'\n     dependency-type: direct:production\n     update-type: version-update:semver-major\n     dependency-group: github-actions\n   ...\n - <csr-id-2717e5b4fdb86099913764fc4e004c0c00c78a08/> bump actions/checkout in the github-actions group\n   Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout).\n   \n   \n   Updates `actions/checkout` from 4 to 5\n   - [Release notes](https://github.com/actions/checkout/releases)\n   - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)\n   - [Commits](https://github.com/actions/checkout/compare/v4...v5)\n   \n   ---\n   updated-dependencies:\n   - dependency-name: actions/checkout\n     dependency-version: '5'\n     dependency-type: direct:production\n     update-type: version-update:semver-major\n     dependency-group: github-actions\n   ...\n\n### Chore (BREAKING)\n\n - <csr-id-b6ea951130ea00be599d30e842cad60bee930f7d/> update `ratatui` to v0.30\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 12 commits contributed to the release.\n - 5 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Thanks Clippy\n\n<csr-read-only-do-not-edit/>\n\n[Clippy](https://github.com/rust-lang/rust-clippy) helped 1 time to make code idiomatic. \n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Merge pull request #48 from GitoxideLabs/updates ([`7a2dbe0`](https://github.com/byron/prodash/commit/7a2dbe013e311e195cfe4c8a71953cdca8d55efa))\n    - Fix ratatui 0.30 compatibility issues ([`40d1a75`](https://github.com/byron/prodash/commit/40d1a759dfeff2795b76fc47544a30ab24d30628))\n    - Update `ratatui` to v0.30 ([`b6ea951`](https://github.com/byron/prodash/commit/b6ea951130ea00be599d30e842cad60bee930f7d))\n    - Merge pull request #47 from GitoxideLabs/updates ([`94b760f`](https://github.com/byron/prodash/commit/94b760ff7b58a6189e93352a7cfabb3312e7b021))\n    - Update various dependnecies ([`7d21bb5`](https://github.com/byron/prodash/commit/7d21bb5cff9a7df720e9e56419235e76a9924f0b))\n    - Thanks clippy ([`aed743a`](https://github.com/byron/prodash/commit/aed743a949e14af905642170e61978e6d1405637))\n    - Merge pull request #45 from GitoxideLabs/dependabot/github_actions/github-actions-76468cb07f ([`0e24337`](https://github.com/byron/prodash/commit/0e24337b8ed9a8bfc59457f4718eafdf4454d527))\n    - Bump actions/checkout in the github-actions group ([`8bdc425`](https://github.com/byron/prodash/commit/8bdc42571ebdda35a05df55129b0a759698b10f6))\n    - Merge pull request #44 from GitoxideLabs/dependabot/github_actions/github-actions-a331d3ec2d ([`f978c6f`](https://github.com/byron/prodash/commit/f978c6f55f5e56b5a755342982210cd8a5cd05f9))\n    - Bump actions/checkout in the github-actions group ([`2717e5b`](https://github.com/byron/prodash/commit/2717e5b4fdb86099913764fc4e004c0c00c78a08))\n    - Merge pull request #43 from slowmman/ft-fix-doc-links ([`423ff21`](https://github.com/byron/prodash/commit/423ff21c7d49b5048731aa159617c70e1b8d4b9f))\n    - Fix a few broken doc links ([`3df0069`](https://github.com/byron/prodash/commit/3df00691101d2d21eeb82df03dbe1ca3bf45263e))\n</details>\n\n## 30.0.1 (2025-06-14)\n\n### Documentation\n\n - <csr-id-b1e26fed9842300bdaf2d0fdae1b789580b91acb/> Don't describe `progress-tree-log` as a default feature\n   Since it is no longer a default feature as of version 30.0.0 (#39).\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 5 commits contributed to the release.\n - 1 day passed between releases.\n - 1 commit was understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v30.0.1 ([`171a83d`](https://github.com/byron/prodash/commit/171a83db7dcba88f3debdd2be63b77a83c622d31))\n    - Merge pull request #42 from EliahKagan/update-doc-next ([`0f261de`](https://github.com/byron/prodash/commit/0f261de75dfcb9b1f865f5219cf8dc2a42c8e39b))\n    - Don't describe `progress-tree-log` as a default feature in readme ([`901359a`](https://github.com/byron/prodash/commit/901359a7d55c62d7dfce48b720b1349d75a0bd95))\n    - Merge pull request #41 from EliahKagan/update-doc ([`cf70e4a`](https://github.com/byron/prodash/commit/cf70e4a5092baa67f41caf6e2c3f6e0646b962f6))\n    - Don't describe `progress-tree-log` as a default feature ([`b1e26fe`](https://github.com/byron/prodash/commit/b1e26fed9842300bdaf2d0fdae1b789580b91acb))\n</details>\n\n## 30.0.0 (2025-06-12)\n\n<csr-id-25f8851b458838044550478e5aa5e44922e063f1/>\n\n**Breaking change**: the default feature `progress-tree-log` was removed. Those in need of\nprogress-by-logging will have to add the feature themselves.\n\n### Documentation\n\n - <csr-id-f59460ed80ee9ba5351824bdcb1862c34036fa74/> Update project URL and add security policy\n   - Update the project URL in `Cargo.toml`, as well as a badge URL in\n   the readme, to point to `GitoxideLabs/prodash`, since the repo\n   was moved (though the old URL will still work as a redirect).\n\n### Other\n\n - <csr-id-25f8851b458838044550478e5aa5e44922e063f1/> bump actions/checkout in the github-actions group\n   Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout).\n   \n   \n   Updates `actions/checkout` from 1 to 4\n   - [Release notes](https://github.com/actions/checkout/releases)\n   - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)\n   - [Commits](https://github.com/actions/checkout/compare/v1...v4)\n   \n   ---\n   updated-dependencies:\n   - dependency-name: actions/checkout\n     dependency-version: '4'\n     dependency-type: direct:production\n     update-type: version-update:semver-major\n     dependency-group: github-actions\n   ...\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 16 commits contributed to the release.\n - 59 days passed between releases.\n - 2 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v30.0.0 ([`85003b1`](https://github.com/byron/prodash/commit/85003b18cf7ddaf1f8f245015c082a8e7a5c4857))\n    - Merge pull request #39 from elijah629/patch-1 ([`427f168`](https://github.com/byron/prodash/commit/427f168684d2930445f1ec25fb873a251a42609d))\n    - Bump major version and write changelog to document breaking change. ([`1bea21c`](https://github.com/byron/prodash/commit/1bea21c5785e8463e035be29b15592c1549590d0))\n    - Progress-tree-log should not be default ([`8e5986d`](https://github.com/byron/prodash/commit/8e5986da4edbf06495469bc423ced62dea9e715f))\n    - Merge pull request #38 from atouchet/badge ([`5f2c06d`](https://github.com/byron/prodash/commit/5f2c06de047313e8f6fe01d5767ce2829a320730))\n    - Fix CI badge ([`3f0cce7`](https://github.com/byron/prodash/commit/3f0cce77cfe440c3dfdbfbe4a0e311038e399768))\n    - Merge pull request #37 from EliahKagan/doc ([`0a272f0`](https://github.com/byron/prodash/commit/0a272f05107ce0ee39e1f16a39e55dfd11d72dd8))\n    - Update project URL and add security policy ([`f59460e`](https://github.com/byron/prodash/commit/f59460ed80ee9ba5351824bdcb1862c34036fa74))\n    - Merge pull request #36 from EliahKagan/run-ci/workflow-permissions ([`cff2029`](https://github.com/byron/prodash/commit/cff2029ba2894549cd7506ec93aea367cccab777))\n    - Set explicit `contents: read` permissions in CI workflow ([`0ad0fa2`](https://github.com/byron/prodash/commit/0ad0fa284e21485c2b10ee02abf1e6ec46d44478))\n    - Merge pull request #35 from Byron/dependabot/github_actions/github-actions-8a9da2b879 ([`6c8261a`](https://github.com/byron/prodash/commit/6c8261af01951fc690c208da14e28d339fd522e5))\n    - Bump actions/checkout in the github-actions group ([`25f8851`](https://github.com/byron/prodash/commit/25f8851b458838044550478e5aa5e44922e063f1))\n    - Merge pull request #34 from EliahKagan/run-ci/gha ([`3b4998b`](https://github.com/byron/prodash/commit/3b4998b9935d5e6ecb0c4166ff0d0913f556d0dc))\n    - Keep action versions up to date with Dependabot ([`12addb4`](https://github.com/byron/prodash/commit/12addb4d992876eeb667a8b1e84f4a46203b86c8))\n    - Make CI easier to run in forks ([`fc6cce5`](https://github.com/byron/prodash/commit/fc6cce5ad3cd22f8d7fb39f7733470038cfdc6ed))\n    - Use more consistent indentation in CI workflow ([`db0f01e`](https://github.com/byron/prodash/commit/db0f01e50412e46b998ef24c8a68ba56e047d260))\n</details>\n\n## 29.0.2 (2025-04-13)\n\nUpdate `bytesize` from v1 to v2.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 4 commits contributed to the release.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v29.0.2 ([`964a86b`](https://github.com/byron/prodash/commit/964a86b4a4b23f9cfdf6e3097eecd6ae7361ef04))\n    - Update changelog prior to release ([`2216036`](https://github.com/byron/prodash/commit/221603660fbf5d5fa14fbc6674c6d460017b769f))\n    - Merge pull request #33 from EliahKagan/bump-bytesize ([`4ff81bf`](https://github.com/byron/prodash/commit/4ff81bf9f61d161a50485c95fb7f3ed9386115c4))\n    - Bump `bytesize` from major version 1 to 2 ([`61dfa7d`](https://github.com/byron/prodash/commit/61dfa7d779e26e4cbbb6e6fa7fdc14294224bfba))\n</details>\n\n## 29.0.1 (2025-03-11)\n\n<csr-id-88d33994876c7b2454cb1d827ebb2933bdecddd2/>\n<csr-id-373b6b1d0a95cff3085bda46ab629728d4fd6e43/>\n\n### Other\n\n - <csr-id-88d33994876c7b2454cb1d827ebb2933bdecddd2/> switch from `humantime` to `jiff`\n   Since prodash switched over to Jiff, Jiff has grown support for the\n   \"friendly\" duration format. It is meant to be a replacement for\n   `humantime` formatting of durations.\n - <csr-id-373b6b1d0a95cff3085bda46ab629728d4fd6e43/> bump jiff to 0.2\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 6 commits contributed to the release.\n - 2 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Thanks Clippy\n\n<csr-read-only-do-not-edit/>\n\n[Clippy](https://github.com/rust-lang/rust-clippy) helped 1 time to make code idiomatic. \n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v29.0.1 ([`58ea133`](https://github.com/byron/prodash/commit/58ea1339d263638b5fa041b86eccd305d28a743a))\n    - Merge pull request #32 from BurntSushi/ag/moar-jiff ([`eaf1c26`](https://github.com/byron/prodash/commit/eaf1c26169a89fca693e5cbf8be4f1cd5fccbf25))\n    - Thanks clippy ([`521c6e1`](https://github.com/byron/prodash/commit/521c6e16464778ca468996fbc6d66c4ffa6d4edb))\n    - Switch from `humantime` to `jiff` ([`88d3399`](https://github.com/byron/prodash/commit/88d33994876c7b2454cb1d827ebb2933bdecddd2))\n    - Bump jiff to 0.2 ([`373b6b1`](https://github.com/byron/prodash/commit/373b6b1d0a95cff3085bda46ab629728d4fd6e43))\n    - Remove unused feature ([`9c476cf`](https://github.com/byron/prodash/commit/9c476cf1a3f31d79d49e56c80b3d2eff7c3f618d))\n</details>\n\n## 29.0.0 (2024-07-29)\n\n<csr-id-5f066f26e347d80edd3cc9480d362b39ce762bd6/>\n<csr-id-aab09862ce8ff64d3c9bafd099e81df89c4c1670/>\n\n### Chore\n\n - <csr-id-5f066f26e347d80edd3cc9480d362b39ce762bd6/> update ratatui to 0.26.0\n   Requires an update to tui-react / crosstermion to work\n\n### Other\n\n - <csr-id-aab09862ce8ff64d3c9bafd099e81df89c4c1670/> switch from `time` to `jiff`\n   This swaps out `time` in favor of `jiff` for getting and formatting the\n   local time.\n   \n   Note that this does add the `%Z` to the format string, which will write\n   out time zone abbreviations like `EDT` along with the local datetime\n   itself. The `time` crate doesn't support this, but jiff's tzdb\n   integration let's it do it.\n\n### New Features (BREAKING)\n\n - <csr-id-f3c3122a571512d0d90edced6f1057f395c9d39f/> upgrade `ratatui` to v0.26\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 9 commits contributed to the release.\n - 3 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v29.0.0 ([`4583a59`](https://github.com/byron/prodash/commit/4583a593d35e248b828dd486b7a6e8305b1a538c))\n    - Upgrade dependencies ([`a5dab6e`](https://github.com/byron/prodash/commit/a5dab6e11760acb413663b74bebe0348b6d3ade8))\n    - Merge branch 'ag/jiff' ([`87f66fb`](https://github.com/byron/prodash/commit/87f66fb59923584905fe463bf6579f56459c4efb))\n    - Switch from `time` to `jiff` ([`aab0986`](https://github.com/byron/prodash/commit/aab09862ce8ff64d3c9bafd099e81df89c4c1670))\n    - Fix CI ([`10800f3`](https://github.com/byron/prodash/commit/10800f3ee3518d5c95e83c8dbe9bbc25da362bd1))\n    - Upgrade `ratatui` to v0.26 ([`f3c3122`](https://github.com/byron/prodash/commit/f3c3122a571512d0d90edced6f1057f395c9d39f))\n    - Avoid running benchmarks on CI for faster runs ([`73bce79`](https://github.com/byron/prodash/commit/73bce79cdfb651ae6ddcb09e297f33389302c40e))\n    - Update to latest versions of `tui-react` and `crosstermion` ([`0cca41f`](https://github.com/byron/prodash/commit/0cca41f4a7a9e6fae995a802b551daa0d3d5ec12))\n    - Update ratatui to 0.26.0 ([`5f066f2`](https://github.com/byron/prodash/commit/5f066f26e347d80edd3cc9480d362b39ce762bd6))\n</details>\n\n## 28.0.0 (2023-12-29)\n\n<csr-id-18686dbd32e6920ab5d7271c32481f7f41eae4de/>\n\n### Chore (BREAKING)\n\n - <csr-id-18686dbd32e6920ab5d7271c32481f7f41eae4de/> upgrade `ratatui` and `crosstermion` to latest versions.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 2 commits contributed to the release.\n - 21 days passed between releases.\n - 1 commit was understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v28.0.0 ([`986909c`](https://github.com/byron/prodash/commit/986909cc4eef785c4f5e9f5f8184723a21ccb721))\n    - Upgrade `ratatui` and `crosstermion` to latest versions. ([`18686db`](https://github.com/byron/prodash/commit/18686dbd32e6920ab5d7271c32481f7f41eae4de))\n</details>\n\n## 27.0.0 (2023-12-07)\n\n### New Features\n\n - <csr-id-6acf6fe22a9ec4f7a16626db556c051c2084ccb2/> Change duration formatting to be more human readable.\n   Note that this changes duration output from something like `69d10h40m`\n   to `69d 10h 40m`.\n\n### Reverted (BREAKING)\n\n - <csr-id-b1fd37d272c59249c151e1dbf498d5e2767f5507/> All `termion`-related features are now removed and obsolete.\n   After the most recent update, certion event-related features in crosstermion\n   stopped working in the context of the GUI, so it's probably best to let it go.\n   \n   By now, `crosstermion` is also very much a more portable replacement.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 8 commits contributed to the release.\n - 2 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Thanks Clippy\n\n<csr-read-only-do-not-edit/>\n\n[Clippy](https://github.com/rust-lang/rust-clippy) helped 1 time to make code idiomatic. \n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v27.0.0 ([`967e99a`](https://github.com/byron/prodash/commit/967e99a034cf1f570864b068d9c00baa54290b19))\n    - Merge branch 'replace-ansi_term' ([`3a50a18`](https://github.com/byron/prodash/commit/3a50a18551c98cb3e0fa8362062e0f6fd41d5d34))\n    - All `termion`-related features are now removed and obsolete. ([`b1fd37d`](https://github.com/byron/prodash/commit/b1fd37d272c59249c151e1dbf498d5e2767f5507))\n    - Thanks clippy ([`3c28eb0`](https://github.com/byron/prodash/commit/3c28eb04e42cf0b2d47e4a6538c2436e8abac274))\n    - Upgrade to `crossterm` v0.27. ([`34397f1`](https://github.com/byron/prodash/commit/34397f1b39dca0d585326728681a70b654db7118))\n    - Change duration formatting to be more human readable. ([`6acf6fe`](https://github.com/byron/prodash/commit/6acf6fe22a9ec4f7a16626db556c051c2084ccb2))\n    - Refactor ([`d032106`](https://github.com/byron/prodash/commit/d0321067244ed3d76eedc0078c881af2ea603c5b))\n    - Replace compound_duration with humantime This is the humantime part of PR https://github.com/Byron/prodash/pull/25 ([`f5143c9`](https://github.com/byron/prodash/commit/f5143c98be0f2f2267c4574805f81a61cd8d613a))\n</details>\n\n## 26.2.2 (2023-09-09)\n\nThis release relaxes trait-bounds of `Count`, `Progress` and `NestedProgress` to allow `?Sized` as well.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 5 commits contributed to the release.\n - 2 days passed between releases.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v26.2.2 ([`bb1eadf`](https://github.com/byron/prodash/commit/bb1eadfc9042cc27b346cc064115a69163f10c05))\n    - Prepare next release ([`414506d`](https://github.com/byron/prodash/commit/414506df859584d17eb8ec755534040b813fa953))\n    - Merge pull request #24 from NobodyXu/fix/dyn ([`c905d5c`](https://github.com/byron/prodash/commit/c905d5cb8d62368bb4891cc732ce613d09d6498b))\n    - Relax bound of `Progress`, `Count` impl for `DynNestedProgressToNestedProgress` ([`967ea46`](https://github.com/byron/prodash/commit/967ea46b71c2bb44f7cd75524d101c4bfd2df0bf))\n    - Fix use of `DynNestedProgress` as trait object ([`7eb69ac`](https://github.com/byron/prodash/commit/7eb69ac3f4946c72a0598b3021153045f3e9626b))\n</details>\n\n## 26.2.1 (2023-09-06)\n\n### Bug Fixes\n\n - <csr-id-53ea2b81292a5062816bb12af0ab4f7931d5d2fa/> Add missing forwardings for various methods.\n   Not having these could lead to incorrect thoughput display.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 2 commits contributed to the release.\n - 1 commit was understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v26.2.1 ([`f37161f`](https://github.com/byron/prodash/commit/f37161f49f65991fd97ad855cf23a4f0ace3c9bb))\n    - Add missing forwardings for various methods. ([`53ea2b8`](https://github.com/byron/prodash/commit/53ea2b81292a5062816bb12af0ab4f7931d5d2fa))\n</details>\n\n## 26.2.0 (2023-09-05)\n\n### New Features\n\n - <csr-id-b3cae191413653feef62808b60b7eea52145e55e/> add `BoxedProgress` type that implements `Progress`.\n   This makes working with boxed progress even more flexible.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 2 commits contributed to the release.\n - 1 commit was understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v26.2.0 ([`8f26902`](https://github.com/byron/prodash/commit/8f2690254576e32624abbfc4b5e18283e7e542f7))\n    - Add `BoxedProgress` type that implements `Progress`. ([`b3cae19`](https://github.com/byron/prodash/commit/b3cae191413653feef62808b60b7eea52145e55e))\n</details>\n\n## 26.1.0 (2023-09-04)\n\n### New Features\n\n - <csr-id-0705a734b401d44657be59e83e4cbf58cb4484a8/> add `progress::AtomicStep` to allow referring to it.\n   Previously, only `StepShared` was available, which implies an `Arc`.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 2 commits contributed to the release.\n - 1 commit was understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v26.1.0 ([`05bc923`](https://github.com/byron/prodash/commit/05bc92395aeeaf498ab46e479decf1ae71382835))\n    - Add `progress::AtomicStep` to allow referring to it. ([`0705a73`](https://github.com/byron/prodash/commit/0705a734b401d44657be59e83e4cbf58cb4484a8))\n</details>\n\n## 26.0.0 (2023-09-04)\n\nThis release is all about making `dyn` possible both for nested progress, as well as for 'simple' one (previously known as `RawProgress`).\nSwitching to this release naturally makes it possible for users of `Progress` to also use `dyn Progress`, as this trait is now object safe (formerly `RawProgress`).\nIf there are compile errors, the code now needs `NestedProgress`, instead of `Progress`, and possibly the import of the `Count` trait.\nFinally, it's recommended to review all usages of `Progress` as they can possibly be replaced with `Count` which provides the guarantee that only counting happens,\nand no change of the progress information itself.\n\n### New Features (BREAKING)\n\n - <csr-id-6aba6e34f3e39bba5e609a0bc780c758cb43c821/> split `Progress` into various super-traits to allow most of them to be dyn-safe.\n   `Progress` is now `NestedProgress`, `RawProgress` is now `Progress`, and there is\n   a new `Count` trait for solely counting things.\n - <csr-id-6c60835ae49487a220b1817bc6cea3ebc8bf1aff/> `Progress::counter()` is now mandatory.\n   This should simplify downstream code and we just accept that we are dealing\n   with a threaded world.\n   This also comes with performance improvements as increments are now 250% faster.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 9 commits contributed to the release.\n - 13 days passed between releases.\n - 2 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v26.0.0 ([`6b94f28`](https://github.com/byron/prodash/commit/6b94f28102b3133e4c1cba3765b55108c528b534))\n    - Merge branch 'simplify' ([`5e21df7`](https://github.com/byron/prodash/commit/5e21df796d4c8ddefd71ee1a35df3b591c6d5e58))\n    - `Progress::counter()` is now mandatory. ([`6c60835`](https://github.com/byron/prodash/commit/6c60835ae49487a220b1817bc6cea3ebc8bf1aff))\n    - Prepare release ([`e1e282a`](https://github.com/byron/prodash/commit/e1e282aa37fc753884407339196809f2b0b72d2d))\n    - Fixup nested dyn-traits ([`5e76abf`](https://github.com/byron/prodash/commit/5e76abfbf8dc18afddea68873c50cce677450a54))\n    - Merge branch 'feat/dyn-progress' into simplify ([`c1590e4`](https://github.com/byron/prodash/commit/c1590e4650a9ffcf96a216f6a9fe82a1cf7cc10e))\n    - Split `Progress` into various super-traits to allow most of them to be dyn-safe. ([`6aba6e3`](https://github.com/byron/prodash/commit/6aba6e34f3e39bba5e609a0bc780c758cb43c821))\n    - Add benchmarks for dyn-traits ([`9d03124`](https://github.com/byron/prodash/commit/9d03124667935314a9d4c8e52886b994967f2671))\n    - Refactor ([`54094b6`](https://github.com/byron/prodash/commit/54094b63289446f0582c6b49a666b5d993625dff))\n</details>\n\n## 25.0.2 (2023-08-22)\n\n<csr-id-05741765491984487beea7326eff9863b669ab51/>\n\n### Chore\n\n - <csr-id-05741765491984487beea7326eff9863b669ab51/> Adjusting changelogs prior to release of prodash v25.0.2\n\n### New Features\n\n - <csr-id-24d0b2aaa58978990fea90c2f3b387e238acf966/> Add new trait `DynProgress` & type `BoxedDynProgress`\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 4 commits contributed to the release.\n - 37 days passed between releases.\n - 2 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v25.0.2 ([`abcc61b`](https://github.com/byron/prodash/commit/abcc61b456e75d4f700f3c476849d7c36c3ece15))\n    - Adjusting changelogs prior to release of prodash v25.0.2 ([`0574176`](https://github.com/byron/prodash/commit/05741765491984487beea7326eff9863b669ab51))\n    - Remove `atty` in favor of `is-terminal` ([`2bfe9ad`](https://github.com/byron/prodash/commit/2bfe9adca357cf48d7310036684eeb85efa5ef46))\n    - Add new trait `DynProgress` & type `BoxedDynProgress` ([`24d0b2a`](https://github.com/byron/prodash/commit/24d0b2aaa58978990fea90c2f3b387e238acf966))\n</details>\n\n## 25.0.1 (2023-07-16)\n\n### Bug Fixes\n\n - <csr-id-17dd8f64563dd96ca454494e24a7f229716f6b1f/> `log` progress now supports a shared counter, just like the tree-item implementation\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 4 commits contributed to the release.\n - 60 days passed between releases.\n - 1 commit was understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v25.0.1 ([`3ad8226`](https://github.com/byron/prodash/commit/3ad8226fd7825669d843a9c1bde001de1b702049))\n    - Merge branch 'log-fixes' ([`a38d22c`](https://github.com/byron/prodash/commit/a38d22cf10068ce4275d65eb3e74652c2977b7e2))\n    - Upgrade criterion to the latest version, it compiles more quickly. ([`78272c0`](https://github.com/byron/prodash/commit/78272c0f2a243b9e07165813a0eacc6ade623b90))\n    - `log` progress now supports a shared counter, just like the tree-item implementation ([`17dd8f6`](https://github.com/byron/prodash/commit/17dd8f64563dd96ca454494e24a7f229716f6b1f))\n</details>\n\n## 25.0.0 (2023-05-16)\n\n### New Features\n\n - <csr-id-8941f4b5b9c0d00dfd7b82c756b128982f163a06/> Introduce the object-safe `RawProgress` trait.\n   It's automatically implemented for `Progress` and allows for more flexible use\n   of progress particularly in leaf nodes. This is useful if a function needs to take\n   multiple types of progress as it is called from different places in the same function.\n   \n   Without dyn-traits, it's not possible to make such call.\n\n### New Features (BREAKING)\n\n - <csr-id-84d96c7b6ab07462d6c20147958d5aa1a58a688e/> Make messaging functions thread-safe by taking shared borrow and requring `Sync`.\n   That way it's possible to share the `RawProgress` object across threads and emit messages,\n   much like a logging system that's more integrated with rendering.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 3 commits contributed to the release.\n - 5 days passed between releases.\n - 2 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v25.0.0 ([`02fcb9b`](https://github.com/byron/prodash/commit/02fcb9bd40ff9c03a46ce68926995a9614328ae0))\n    - Make messaging functions thread-safe by taking shared borrow and requring `Sync`. ([`84d96c7`](https://github.com/byron/prodash/commit/84d96c7b6ab07462d6c20147958d5aa1a58a688e))\n    - Introduce the object-safe `RawProgress` trait. ([`8941f4b`](https://github.com/byron/prodash/commit/8941f4b5b9c0d00dfd7b82c756b128982f163a06))\n</details>\n\n## 24.0.0 (2023-05-11)\n\n<csr-id-fe5d01736179271f6b7bf20367f5d0e2bb616c4a/>\n\n### Chore (BREAKING)\n\n - <csr-id-fe5d01736179271f6b7bf20367f5d0e2bb616c4a/> switch from `tui` to `ratatui`.\n   The latter is a maintained fork.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 4 commits contributed to the release.\n - 1 commit was understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Thanks Clippy\n\n<csr-read-only-do-not-edit/>\n\n[Clippy](https://github.com/rust-lang/rust-clippy) helped 1 time to make code idiomatic. \n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v24.0.0 ([`41ad0a4`](https://github.com/byron/prodash/commit/41ad0a45ea39e6b283f808768ea60495b3d2b65f))\n    - Merge branch 'ratatui' ([`4920457`](https://github.com/byron/prodash/commit/492045793a23be9ebda7310d89593288a2bd3340))\n    - Thanks clippy ([`25356a3`](https://github.com/byron/prodash/commit/25356a369c345b1e89f7c6c356ff7142020849de))\n    - Switch from `tui` to `ratatui`. ([`fe5d017`](https://github.com/byron/prodash/commit/fe5d01736179271f6b7bf20367f5d0e2bb616c4a))\n</details>\n\n## 23.1.2 (2023-03-11)\n\n### Bug Fixes\n\n - <csr-id-7966f79cc7009acb33761cee70398b05b0006cc1/> line renderer now properly detects changes.\n   Previously change-detection was implemented based on the assumption that\n   the progress tree is copied entirely. Now, however, the interesting values\n   are shared.\n   \n   The change-detection was adjusted to keep the state's hash of the most recent\n   drawing, instead of doing everything in line, which saves time hashing as well.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 2 commits contributed to the release.\n - 9 days passed between releases.\n - 1 commit was understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v23.1.2 ([`c15f8db`](https://github.com/byron/prodash/commit/c15f8db0a7221ad6326544685200b68cb9ff5ddd))\n    - Line renderer now properly detects changes. ([`7966f79`](https://github.com/byron/prodash/commit/7966f79cc7009acb33761cee70398b05b0006cc1))\n</details>\n\n## 23.1.1 (2023-03-02)\n\nA maintenance release without user-facing changes.\n\nMost notably, `parking_lot` was upgraded to the latest version.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 3 commits contributed to the release.\n - 2 days passed between releases.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v23.1.1 ([`a91c52f`](https://github.com/byron/prodash/commit/a91c52f0407a8adf8f93ad66796e1979e08ce126))\n    - Prepare changelog ([`1a4eb9b`](https://github.com/byron/prodash/commit/1a4eb9b5dea3e100b188be956ab7670f0b8d5ad6))\n    - Upgrade dependencies, particularly `parking_lot` ([`7ae8a07`](https://github.com/byron/prodash/commit/7ae8a0793752b713c6605be45688ca81fbb7e75e))\n</details>\n\n## 23.1.0 (2023-02-28)\n\n### New Features\n\n - <csr-id-6f966b4f859f1b02775dcb3461bacf46b46ab707/> improve performance of `progress::tree` operations by more than 50%.\n   This was done by implementing shared state in a simple Mutex protected hashmap\n   which for typical programs with less contention is faster than using the `dashmap`\n   crate.\n   \n   However, for those who know they need it, the previous implementation is still available\n   in with the `progress-tree-hp-hashmap` feature toggle.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 4 commits contributed to the release.\n - 61 days passed between releases.\n - 1 commit was understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v23.1.0 ([`45b4b7e`](https://github.com/byron/prodash/commit/45b4b7ea06b08cb30bd1f1ca2c05874bb0cf19ce))\n    - No need for `unsound-local-offset` anymore. ([`c53ab9d`](https://github.com/byron/prodash/commit/c53ab9d5d238be4aaabfb8a990c80ca1a7c58dec))\n    - Make fmt ([`490336f`](https://github.com/byron/prodash/commit/490336f9920ccde0ebe8d142dc51b390e9afc899))\n    - Improve performance of `progress::tree` operations by more than 50%. ([`6f966b4`](https://github.com/byron/prodash/commit/6f966b4f859f1b02775dcb3461bacf46b46ab707))\n</details>\n\n## 23.0.0 (2022-12-29)\n\n### New Features (BREAKING)\n\n - <csr-id-a1db1b27fc7f14be052bbfc660c9c9b174c1d3cc/> Implement `Hash` for `Task` to avoid redrawing if nothing changes with the Line renderer.\n   That way, if everything stops due to a user prompt, the user's input won't be clobbered\n   continnuously.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 2 commits contributed to the release.\n - 23 days passed between releases.\n - 1 commit was understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v23.0.0 ([`d527e4b`](https://github.com/byron/prodash/commit/d527e4be160ac80714deeaca11b8ed677f8d842f))\n    - Implement `Hash` for `Task` to avoid redrawing if nothing changes with the Line renderer. ([`a1db1b2`](https://github.com/byron/prodash/commit/a1db1b27fc7f14be052bbfc660c9c9b174c1d3cc))\n</details>\n\n## 22.1.0 (2022-12-06)\n\n### New Features\n\n - <csr-id-ea9aa5815ef2d1c734b85c185ddb65ac731b1195/> `progress::Key` now supports 6 levels of hierarchy instead of 4.\n   That way it's less likely that surprises occour of more than necessary\n   levels are added.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 2 commits contributed to the release.\n - 1 day passed between releases.\n - 1 commit was understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v22.1.0 ([`2ae17c5`](https://github.com/byron/prodash/commit/2ae17c5bd7ab91b9e05a16fb4f771ce0ce0001f4))\n    - `progress::Key` now supports 6 levels of hierarchy instead of 4. ([`ea9aa58`](https://github.com/byron/prodash/commit/ea9aa5815ef2d1c734b85c185ddb65ac731b1195))\n</details>\n\n## 22.0.0 (2022-12-05)\n\n<csr-id-46aeffd13cda49146c8a33e93c8c9b0fbcb15c8b/>\n\n### Chore\n\n - <csr-id-46aeffd13cda49146c8a33e93c8c9b0fbcb15c8b/> switch to Rust edition 2021\n\n### Changed (BREAKING)\n\n - <csr-id-53cb09dc0314b0e8ce58dc50e0c07a053b963ccd/> remove `Tree` and `TreeOptions` in favor of `tree::Root` and `tree::root::Options`.\n   Previously it was confusing what a tree Root actually is due to the\n   rename, and ambiguity isn't what we would want here.\n\n### New Features (BREAKING)\n\n - <csr-id-edab37364276864d45241c2946173388a0602f23/> `From<tree::root::Options> for tree::Root`, `tree::root::Options::create()` returns `tree::Root` instead of `Arc`.\n   That way we won't be forced to produce an `Arc` if it's not needed.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 5 commits contributed to the release.\n - 11 days passed between releases.\n - 3 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v22.0.0 ([`8b78fe9`](https://github.com/byron/prodash/commit/8b78fe9614a584096f4fefed38a8965266caf2e9))\n    - Switch to Rust edition 2021 ([`46aeffd`](https://github.com/byron/prodash/commit/46aeffd13cda49146c8a33e93c8c9b0fbcb15c8b))\n    - Make fmt ([`6e90cb9`](https://github.com/byron/prodash/commit/6e90cb965377762920d4dbb5debe8d47a4be89a2))\n    - `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))\n    - Remove `Tree` and `TreeOptions` in favor of `tree::Root` and `tree::root::Options`. ([`53cb09d`](https://github.com/byron/prodash/commit/53cb09dc0314b0e8ce58dc50e0c07a053b963ccd))\n</details>\n\n## 21.1.0 (2022-11-23)\n\n### New Features\n\n - <csr-id-c332a6f266a6ae0cacf19cb523e551bb63c1e7ea/> identify each progress item with `Id` using `add_child_with_id()`.\n   An `Id` is four bytes like b\"TREE\" that are stable and\n   identify progress items (as created by `add_child(…)` within\n   a function call.\n   \n   Callers may use this knowledge to pick specific progress items\n   for consumption, instead of trying to rely on identifying tasks\n   by name which may change.\n   \n   The identifier can also be queried with `Progress::id()`, even\n   though it could be `prodash::progress::UNKNOWN` if the progress\n   item wasn't created with `add_child_with_id()`.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 4 commits contributed to the release.\n - 1 commit was understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v21.1.0 ([`6327e15`](https://github.com/byron/prodash/commit/6327e1587d6886f4279ec037ad587dab52a7cedf))\n    - `make fmt` ([`5415acc`](https://github.com/byron/prodash/commit/5415acc4cae7bab81a22758fe969bdb8b3deac13))\n    - Make it easy to format 'use'es in the codebase. ([`5b8e54d`](https://github.com/byron/prodash/commit/5b8e54d959f895d762a4a77d194f3947a6302783))\n    - Identify each progress item with `Id` using `add_child_with_id()`. ([`c332a6f`](https://github.com/byron/prodash/commit/c332a6f266a6ae0cacf19cb523e551bb63c1e7ea))\n</details>\n\n## 21.0.0 (2022-10-17)\n\n### New Features\n\n - <csr-id-3347a0294c5b95270c34de9f396214353baea36d/> `impl Progress for &mut T: where T: Progress`.\n   This makes it possible to hand borrowed progress implementations to\n   functions that need progress reporting, making the usage of progress\n   easier.\n\n### New Features (BREAKING)\n\n - <csr-id-300181bdd4b2ef1822dddd1fe814d7e3e5b26779/> remove `Progress: 'static` requirement.\n   This requirement can be added where used and where needed, and\n   originally snuck in because it was easier and `Progress` implementations\n   typically are `'static` as well.\n   \n   However, that requirement made it impossible to implement `Progoress`\n   for `&mut T where T: Progress`.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 3 commits contributed to the release.\n - 27 days passed between releases.\n - 2 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v21.0.0 ([`d7fcf5b`](https://github.com/byron/prodash/commit/d7fcf5bb5a6bad8225c22ce6df848835f5790847))\n    - `impl Progress for &mut T: where T: Progress`. ([`3347a02`](https://github.com/byron/prodash/commit/3347a0294c5b95270c34de9f396214353baea36d))\n    - Remove `Progress: 'static` requirement. ([`300181b`](https://github.com/byron/prodash/commit/300181bdd4b2ef1822dddd1fe814d7e3e5b26779))\n</details>\n\n## 20.2.0 (2022-09-20)\n\n### New Features\n\n - <csr-id-4904065a51594b5bc4f32a247e513b45093373fa/> Add `Progress::set_max()` to set the highgest expected progress value.\n\n### Bug Fixes\n\n - <csr-id-414bc71e79475335345dbc529566a6efc3afee23/> don't reset the shared counter value on `init`.\n   It's possible to re-initialize the progress, and when that's done\n   it would detach the counter from previous instances that might have\n   been observed by callers to `counter()`, which is surprising.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 4 commits contributed to the release.\n - 2 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v20.2.0 ([`f49f470`](https://github.com/byron/prodash/commit/f49f4703f5d09f9bbe8ec32bd249c42bd129d7ea))\n    - Don't reset the shared counter value on `init`. ([`414bc71`](https://github.com/byron/prodash/commit/414bc71e79475335345dbc529566a6efc3afee23))\n    - Add `Progress::set_max()` to set the highgest expected progress value. ([`4904065`](https://github.com/byron/prodash/commit/4904065a51594b5bc4f32a247e513b45093373fa))\n    - Make more explicit what is cloned (without altering actual behaviour) ([`b023efd`](https://github.com/byron/prodash/commit/b023efdc9c076f15cef1978ad95d932f782bdea7))\n</details>\n\n## 20.1.1 (2022-09-20)\n\n### Bug Fixes\n\n - <csr-id-a0e7da7d08331eeeea8ca0cb6e349fe32ce876bc/> implement `Progress::counter()` for all utility types.\n   This was forgotten previously as there was a default implementation\n   right from the start.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 2 commits contributed to the release.\n - 1 commit was understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v20.1.1 ([`d152701`](https://github.com/byron/prodash/commit/d1527018e5156921c3263c9d9afafad8c488a3b9))\n    - Implement `Progress::counter()` for all utility types. ([`a0e7da7`](https://github.com/byron/prodash/commit/a0e7da7d08331eeeea8ca0cb6e349fe32ce876bc))\n</details>\n\n## 20.1.0 (2022-09-20)\n\n### New Features\n\n - <csr-id-08be317dbd77f0fdb9673b9c24c239ad0d5078c3/> `Progress::counter()` returns a shared step counter.\n   This is useful if multiple threads want to access the same progress, without the need\n   for provide each their own progress and aggregating the result.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 4 commits contributed to the release.\n - 5 days passed between releases.\n - 1 commit was understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Thanks Clippy\n\n<csr-read-only-do-not-edit/>\n\n[Clippy](https://github.com/rust-lang/rust-clippy) helped 1 time to make code idiomatic. \n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v20.1.0 ([`6591872`](https://github.com/byron/prodash/commit/65918722da151b756f6a3facc3577bd2002feadc))\n    - Fix compile warnings for some configurations ([`fe0ac06`](https://github.com/byron/prodash/commit/fe0ac0659c5faba9b9c0b699850ffc9e66b566f0))\n    - Thanks clippy ([`deb7791`](https://github.com/byron/prodash/commit/deb77916c1ced591410aba2593284c6a1345d426))\n    - `Progress::counter()` returns a shared step counter. ([`08be317`](https://github.com/byron/prodash/commit/08be317dbd77f0fdb9673b9c24c239ad0d5078c3))\n</details>\n\n## 20.0.1 (2022-09-15)\n\n### Bug Fixes\n\n - <csr-id-80a4850802023e5a0f6fe85e6af416aaeb729e21/> Allow builds to succeed on Windows by not registering SIGWINCH signal.\n   Without said signal, the render line will not automatically resize\n   anymore, which in theory can be compensated for by re-obtaining\n   the terminal dimensions every now and then (currently not done).\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 5 commits contributed to the release.\n - 2 days passed between releases.\n - 1 commit was understood as [conventional](https://www.conventionalcommits.org).\n - 1 unique issue was worked on: [#12](https://github.com/byron/prodash/issues/12)\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **[#12](https://github.com/byron/prodash/issues/12)**\n    - Allow builds to succeed on Windows by not registering SIGWINCH signal. ([`80a4850`](https://github.com/byron/prodash/commit/80a4850802023e5a0f6fe85e6af416aaeb729e21))\n    - Only register SIGWINCH signal on unix ([`5de765b`](https://github.com/byron/prodash/commit/5de765b6c134be6c439583dd89297fe2b3f1e41f))\n    - Adjust CI configuration to catch more cross-platform errors ([`b7e0f2c`](https://github.com/byron/prodash/commit/b7e0f2c97bc6c384dfbb5ed390909a5a6850bb74))\n * **Uncategorized**\n    - Release prodash v20.0.1 ([`b98595b`](https://github.com/byron/prodash/commit/b98595b8cc9fd2f0bf3ea8cc22fbd7cf6cb07ab1))\n    - Do away with unsafe code by using safe wrappers instead. ([`447aa0f`](https://github.com/byron/prodash/commit/447aa0f518aaa97dd061813a501e6a0b8512dd88))\n</details>\n\n## 20.0.0 (2022-09-12)\n\n<csr-id-a3b26782dc074c469b5fc480595d2ac9ef8bc9d0/>\n\n### New Features\n\n - <csr-id-bab2ea09089e2eb8e4e826bb17211a444aa35f31/> line renderer adjusts when resizing the terminal.\n\n### Chore (BREAKING)\n\n - <csr-id-a3b26782dc074c469b5fc480595d2ac9ef8bc9d0/> upgrade dependencies to tui `0.19` and crossterm `0.25`\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 3 commits contributed to the release.\n - 175 days passed between releases.\n - 2 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v20.0.0 ([`54e0a6a`](https://github.com/byron/prodash/commit/54e0a6ae39ff53b5c6e7abe9f44d003b833bfed6))\n    - Line renderer adjusts when resizing the terminal. ([`bab2ea0`](https://github.com/byron/prodash/commit/bab2ea09089e2eb8e4e826bb17211a444aa35f31))\n    - Upgrade dependencies to tui `0.19` and crossterm `0.25` ([`a3b2678`](https://github.com/byron/prodash/commit/a3b26782dc074c469b5fc480595d2ac9ef8bc9d0))\n</details>\n\n## 19.0.1 (2022-03-20)\n\n### Bug Fixes\n\n - <csr-id-dbca35f478253176e852c177e79b3253eaafe3bd/> line renderer will clear previous lines if progress is lost\n   Previously it would just exit its main loop and leave lines on screen.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 3 commits contributed to the release.\n - 1 commit was understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v19.0.1 ([`a0c88b7`](https://github.com/byron/prodash/commit/a0c88b7ce0541edd9b6f677454bc0fe694b16fc7))\n    - Line renderer will clear previous lines if progress is lost ([`dbca35f`](https://github.com/byron/prodash/commit/dbca35f478253176e852c177e79b3253eaafe3bd))\n    - Fix benchmark compilation ([`39bc237`](https://github.com/byron/prodash/commit/39bc2379c966244746b4ce361e576997159e19aa))\n</details>\n\n## 19.0.0 (2022-03-20)\n\n### New Features\n\n - <csr-id-cba841c828142c0dd028dd9413c31f509f2bbb1b/> Improve render-log performance greatly.\n   Previously it would check the current time each time somebody\n   wants to log on any logger, greatly reducing performance as\n   it would block on the mutex rust-std uses internally.\n   \n   Now we use a single thread to provide information about whether or not\n   we may log, whose lifetime is bound to all of the log instances it\n   governs.\n\n### New Features (BREAKING)\n\n - <csr-id-b6d5245344bde92672cd98aecacb5d94ecca4e19/> Allow rendererers to respond to dropped progress roots\n   Previously it needed extra effort to communicate that a the computation\n   was done and the renderer should stop rendering progress.\n   \n   Now they only obtain a weak progress instance so it can drop if the\n   computation is done, terminating it naturally and in time.\n   \n   Note that in case of the TUI, it would still be needed to respond\n   to the GUI having shut down due to user request.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 4 commits contributed to the release over the course of 41 calendar days.\n - 41 days passed between releases.\n - 2 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v19.0.0 ([`fda577e`](https://github.com/byron/prodash/commit/fda577e72d831d455289a6786262ea94f861063e))\n    - Improve render-log performance greatly. ([`cba841c`](https://github.com/byron/prodash/commit/cba841c828142c0dd028dd9413c31f509f2bbb1b))\n    - Allow rendererers to respond to dropped progress roots ([`b6d5245`](https://github.com/byron/prodash/commit/b6d5245344bde92672cd98aecacb5d94ecca4e19))\n    - Actually, the correct dashmap version is 5.1 ([`6bdb7e8`](https://github.com/byron/prodash/commit/6bdb7e8ea3a65d51e69436799de3f9862b55dba4))\n</details>\n\n## 18.0.2 (2022-02-07)\n\n<csr-id-e4f2ab842b34f4a4fe9b2f4c34b664a2e3dba200/>\n\n### Chore\n\n - <csr-id-e4f2ab842b34f4a4fe9b2f4c34b664a2e3dba200/> Upgrade dashmap to 5.0.1 (with security fix)\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 2 commits contributed to the release.\n - 5 days passed between releases.\n - 1 commit was understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v18.0.2 ([`69f4295`](https://github.com/byron/prodash/commit/69f42953ec69da4c2c34c8c137dfe7b7a1c12598))\n    - Upgrade dashmap to 5.0.1 (with security fix) ([`e4f2ab8`](https://github.com/byron/prodash/commit/e4f2ab842b34f4a4fe9b2f4c34b664a2e3dba200))\n</details>\n\n## 18.0.1 (2022-02-01)\n\n### Bug Fixes\n\n - <csr-id-a1f8aa650d1a1d2ac53025e29c71782b1cab58c5/> Downgrade to dashmap 4.0\n   While waiting for unoundness to be resolved.\n   \n   See the issue for details: https://github.com/xacrimon/dashmap/issues/167\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 2 commits contributed to the release.\n - 9 days passed between releases.\n - 1 commit was understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v18.0.1 ([`e9769dd`](https://github.com/byron/prodash/commit/e9769dd7ad5c6f4618dd138aea74aba10fba69b3))\n    - Downgrade to dashmap 4.0 ([`a1f8aa6`](https://github.com/byron/prodash/commit/a1f8aa650d1a1d2ac53025e29c71782b1cab58c5))\n</details>\n\n## 18.0.0 (2022-01-23)\n\n### New Features (BREAKING)\n\n - <csr-id-482b54f9c584b0e2d22c53622c9f7ad45b79ad2c/> upgrade to tui 0.17\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 3 commits contributed to the release.\n - 19 days passed between releases.\n - 1 commit was understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v18.0.0 ([`10531ae`](https://github.com/byron/prodash/commit/10531ae35570af6003275a92ba40612a5e23678f))\n    - Prepare changelog ([`a7a58c0`](https://github.com/byron/prodash/commit/a7a58c0fb6ea1068b914a653eb33c96dc157dc06))\n    - Upgrade to tui 0.17 ([`482b54f`](https://github.com/byron/prodash/commit/482b54f9c584b0e2d22c53622c9f7ad45b79ad2c))\n</details>\n\n## 17.0.0 (2022-01-03)\n\n### New Features (BREAKING)\n\n - <csr-id-46214a306793a6a9f304f854dfd7396ceaf433d3/> Add `MessageLevel` parameter to `Progress::show_throughput_with(…, level)`\n   This allows to use message level for highlighting of certain\n   throughputs and results.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 2 commits contributed to the release.\n - 1 commit was understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v17.0.0 ([`9085aaa`](https://github.com/byron/prodash/commit/9085aaaa0a7844c1d4b4afcec4b688f5c398ba87))\n    - Add `MessageLevel` parameter to `Progress::show_throughput_with(…, level)` ([`46214a3`](https://github.com/byron/prodash/commit/46214a306793a6a9f304f854dfd7396ceaf433d3))\n</details>\n\n## 16.1.3 (2022-01-03)\n\n### Bug Fixes\n\n - <csr-id-2fe3eebbd62ddd9beacc294eb6ec04b4b39a26f6/> `Progress::init(None, None)` now resets the progress entirely\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 2 commits contributed to the release.\n - 2 days passed between releases.\n - 1 commit was understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v16.1.3 ([`6551d45`](https://github.com/byron/prodash/commit/6551d45837aecc853f78e848b4257b559287a6ac))\n    - `Progress::init(None, None)` now resets the progress entirely ([`2fe3eeb`](https://github.com/byron/prodash/commit/2fe3eebbd62ddd9beacc294eb6ec04b4b39a26f6))\n</details>\n\n## 16.1.2 (2022-01-01)\n\n### Bug Fixes\n\n - <csr-id-aa70a27ef1de930fdd00266239ee262b52a681a1/> reset the shared value on init to avoid keeping the previously set value.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 2 commits contributed to the release.\n - 4 days passed between releases.\n - 1 commit was understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v16.1.2 ([`3f16e6e`](https://github.com/byron/prodash/commit/3f16e6e31ee2b6df70841900d5b118f6533f9d2e))\n    - Reset the shared value on init to avoid keeping the previously set value. ([`aa70a27`](https://github.com/byron/prodash/commit/aa70a27ef1de930fdd00266239ee262b52a681a1))\n</details>\n\n## 16.1.1 (2021-12-27)\n\n### Bug Fixes\n\n - <csr-id-ca5f544594facc92c8744b293c7287dcffe065e5/> correct signature of new 'running()' method\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 2 commits contributed to the release.\n - 1 commit was understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v16.1.1 ([`f426c20`](https://github.com/byron/prodash/commit/f426c20dca5440f6d39c979a8a851ee444a2c388))\n    - Correct signature of new 'running()' method ([`ca5f544`](https://github.com/byron/prodash/commit/ca5f544594facc92c8744b293c7287dcffe065e5))\n</details>\n\n## 16.1.0 (2021-12-27)\n\n### New Features\n\n - <csr-id-3886754817ac528178b8ea326d0b4d576168eb28/> Setting the progress value is now 9x faster\n   This is accomplished at the cost of not autoamtically setting the\n   progress to 'running' anymore when the progress is set.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 3 commits contributed to the release.\n - 8 days passed between releases.\n - 1 commit was understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v16.1.0 ([`34ae001`](https://github.com/byron/prodash/commit/34ae0014627c5fde785691ffc6583c52b629da51))\n    - Setting the progress value is now 9x faster ([`3886754`](https://github.com/byron/prodash/commit/3886754817ac528178b8ea326d0b4d576168eb28))\n    - An experiment to show we don't want to rely on dashmap for this ([`4f527c1`](https://github.com/byron/prodash/commit/4f527c12caa85018de293762194f9a2aed5daaea))\n</details>\n\n## 16.0.1 (2021-12-19)\n\n<csr-id-e6f53d59ef1aef027a2aad5b164535c6ca0d620b/>\n\n### Chore\n\n - <csr-id-e6f53d59ef1aef027a2aad5b164535c6ca0d620b/> upgrade dashmap to latest version\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 6 commits contributed to the release over the course of 47 calendar days.\n - 109 days passed between releases.\n - 1 commit was understood as [conventional](https://www.conventionalcommits.org).\n - 1 unique issue was worked on: [#8](https://github.com/byron/prodash/issues/8)\n\n### Thanks Clippy\n\n<csr-read-only-do-not-edit/>\n\n[Clippy](https://github.com/rust-lang/rust-clippy) helped 1 time to make code idiomatic. \n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **[#8](https://github.com/byron/prodash/issues/8)**\n    - Run `cargo changelog --write` for an improved changelog ([`a1054e8`](https://github.com/byron/prodash/commit/a1054e89b6f5bbdead1a5c2f975cdce0da0700e7))\n * **Uncategorized**\n    - Release prodash v16.0.1 ([`9418df2`](https://github.com/byron/prodash/commit/9418df2660e5cb17c9906f86fb379c0d22c7ddb7))\n    - Upgrade dashmap to latest version ([`e6f53d5`](https://github.com/byron/prodash/commit/e6f53d59ef1aef027a2aad5b164535c6ca0d620b))\n    - Cleanup changelog ([`5aa6275`](https://github.com/byron/prodash/commit/5aa627523536a85c382f0da20636963387b437bd))\n    - Thanks clippy ([`c1258e2`](https://github.com/byron/prodash/commit/c1258e250207889c62ef3208590d84185752e1a2))\n    - Looks like array syntax isn't supported anymore ([`bfbce01`](https://github.com/byron/prodash/commit/bfbce01af9b90f5ae6b0490d6d7cdc29e05a1338))\n</details>\n\n## v16.0.0 (2021-08-31)\n\n### Improvements\n\n- Use `time` version 0.3 when the `local-time` feature is enabled\n\n### Breaking\n\n- rename cargo feature `localtime` to `local-time`\n- The `local-time` feature is not the default anymore, enable it using the `RUSTFLAGS=\"--cfg unsound_local_offset\"` environment when building the binary.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 4 commits contributed to the release.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v16.0.0 ([`fec0304`](https://github.com/byron/prodash/commit/fec0304eba34db15c981a040c21e0371a5ac50d2))\n    - Improve docs ([`71d66ec`](https://github.com/byron/prodash/commit/71d66ecd2b3330759890f9056ea686c269fbe63a))\n    - Upgrade to time 0.3 and opt in to unsound features for example binaries (unix only) ([`fb3e0b0`](https://github.com/byron/prodash/commit/fb3e0b035cbe911d72a544a779d46cdda7b8105c))\n    - Rename 'localtime' to 'local-time' (cargo feature)… ([`b6ee809`](https://github.com/byron/prodash/commit/b6ee8096d22ce7a97c9bce9bcddad966a05b365f))\n</details>\n\n## v15.0.1 (2021-08-31)\n\n* crosstermion is optional for some renderers (again)\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 2 commits contributed to the release.\n - 25 days passed between releases.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Release prodash v15.0.1 ([`72ea8a9`](https://github.com/byron/prodash/commit/72ea8a9565da457d6f1881bc64e6223c57a951a0))\n    - Fix manifest key! ([`d9275f2`](https://github.com/byron/prodash/commit/d9275f22c3845fb21f601d5d426a0949a398c47d))\n</details>\n\n## v15.0.0 (2021-08-05)\n\n* Upgrade to TUI v0.16\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 4 commits contributed to the release over the course of 32 calendar days.\n - 50 days passed between releases.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Thanks Clippy\n\n<csr-read-only-do-not-edit/>\n\n[Clippy](https://github.com/rust-lang/rust-clippy) helped 1 time to make code idiomatic. \n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Upgrade to tui 0.16 ([`7b45023`](https://github.com/byron/prodash/commit/7b45023eb724429b2e0ba8794d7e80f867b51456))\n    - Thanks clippy ([`5ebf8b4`](https://github.com/byron/prodash/commit/5ebf8b4af0c19472a0efb29a2ac95dbe47c3fdd8))\n    - Dependency update ([`30d61c5`](https://github.com/byron/prodash/commit/30d61c5f57ab9d1200d237a23d272a43f1609956))\n    - Use pin instead of boxing unnecessarily. ([`aa715ae`](https://github.com/byron/prodash/commit/aa715ae0903cbc95a0e1245e5fa62e3b505eae35))\n</details>\n\n## v14.0.0 (2021-06-16)\n\n* Swap `ctrlc` crate with `signal-hook` which is a must in library crates. `ctrlc` is only for applications\n  who can control the single handler that it installs entirely.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 3 commits contributed to the release.\n - 38 days passed between releases.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - (cargo-release) version 14.0.0 ([`a6492c6`](https://github.com/byron/prodash/commit/a6492c697f6afa79ee9bdec7c355d7f4388e5a6c))\n    - Prepare release ([`c2e3193`](https://github.com/byron/prodash/commit/c2e3193dab419c5bc94ee6138430248c0d36e299))\n    - Use signal-hook instead of ctrlc ([`a733267`](https://github.com/byron/prodash/commit/a7332677625fecf18566a05498b5e9bbd2108bc6))\n</details>\n\n## v13.1.1 (2021-05-08)\n\n* Fix compile error (and protect from that regression) if `render-line-autoconfigure` was enabled.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 2 commits contributed to the release.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - (cargo-release) version 13.1.1 ([`d120f2f`](https://github.com/byron/prodash/commit/d120f2fb4f46fc66c4533d5c4f5f4ee1aeec7fd4))\n    - Fix compile issue, prep for patch release ([`584768a`](https://github.com/byron/prodash/commit/584768a07b37d6c11928dc022c44cf2d5c2c7e08))\n</details>\n\n## v13.1.0 (2021-05-08)\n\n* With the `render-line-autoconfigure` feature toggle, the new\n  `Options::default().auto_configure(…)` method allows to adapt to the terminal/non-terminal autmatically.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 3 commits contributed to the release.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - (cargo-release) version 13.1.0 ([`8aedcab`](https://github.com/byron/prodash/commit/8aedcab872bfbad27c4ca120370e428d1a227324))\n    - Update changelog ([`ffd4a1f`](https://github.com/byron/prodash/commit/ffd4a1f15a4ca0872f535fc2460890a9328ba7fd))\n    - Add render-line-autoconfigure feature toggle ([`212ce36`](https://github.com/byron/prodash/commit/212ce369c290a14e99be91e7d5cafb154154cf8b))\n</details>\n\n## v13.0.1 (2021-05-08)\n\n* The line renderer won't try to hide the cursor if the output isn't a terminal.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 6 commits contributed to the release over the course of 5 calendar days.\n - 5 days passed between releases.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Thanks Clippy\n\n<csr-read-only-do-not-edit/>\n\n[Clippy](https://github.com/rust-lang/rust-clippy) helped 1 time to make code idiomatic. \n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - (cargo-release) version 13.0.1 ([`0d41f11`](https://github.com/byron/prodash/commit/0d41f111875598a90b7a2a7a8f4e0872feb1a285))\n    - Prepare point release ([`8bfd42b`](https://github.com/byron/prodash/commit/8bfd42b0b03fc2b108ec68ea925e3d27742789da))\n    - Don't try to hide the cursor if the output isn't a terminal ([`26c7497`](https://github.com/byron/prodash/commit/26c74976ee4e6c3eec89dddf1a90e6ce22539dd8))\n    - More robust example for handling ttys and TUI ([`4d1dd31`](https://github.com/byron/prodash/commit/4d1dd313430054f17b2daddad8bd9d81061e60b0))\n    - Thanks clippy ([`13404f5`](https://github.com/byron/prodash/commit/13404f5f34d49e5607fa77e56bef0669b24c4adb))\n    - Remove crosstermion, it now lives in https://github.com/Byron/tui-crates ([`f560f84`](https://github.com/byron/prodash/commit/f560f84436c1bd2b5af8b0af44e8e86200d22cc2))\n</details>\n\n## v13.0.0 (2021-05-03)\n\n<csr-id-e3665a2100fba190fc0f047ff05f2904f4dcaf4a/>\n<csr-id-c91d410e8d6242b78c44119155b5fc3b2956d111/>\n<csr-id-03d1c2067778fb6ec231bf18dd587046a03434bc/>\n\n* Upgrade to TUI v0.15\n\n### Other\n\n - <csr-id-e3665a2100fba190fc0f047ff05f2904f4dcaf4a/> prep release\n - <csr-id-c91d410e8d6242b78c44119155b5fc3b2956d111/> prepare release\n - <csr-id-03d1c2067778fb6ec231bf18dd587046a03434bc/> Upgrade to tui 0.15\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 13 commits contributed to the release over the course of 109 calendar days.\n - 110 days passed between releases.\n - 3 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - (cargo-release) version 13.0.0 ([`c0a97fa`](https://github.com/byron/prodash/commit/c0a97fa648cb2a0491d09bd0fbdd1daa6d6e290e))\n    - Prep release ([`e3665a2`](https://github.com/byron/prodash/commit/e3665a2100fba190fc0f047ff05f2904f4dcaf4a))\n    - Upgrade to tui 0.15 ([`edd11be`](https://github.com/byron/prodash/commit/edd11be0aa6ed84370603452286182a28c577961))\n    - Prepare release ([`c91d410`](https://github.com/byron/prodash/commit/c91d410e8d6242b78c44119155b5fc3b2956d111))\n    - Upgrade to tui 0.15 ([`03d1c20`](https://github.com/byron/prodash/commit/03d1c2067778fb6ec231bf18dd587046a03434bc))\n    - Revert \"Allow most recent version of 'time' crate\" ([`ee8ab91`](https://github.com/byron/prodash/commit/ee8ab91f74d6e598f59947d08405ca1f7fdb2785))\n    - Allow most recent version of 'time' crate ([`300aa7b`](https://github.com/byron/prodash/commit/300aa7b29ff6bad31197368340bebd2e2a5dfea0))\n    - Run actions on main ([`a2b3037`](https://github.com/byron/prodash/commit/a2b303730602c1b6e4a63475b134f87d640b6d2f))\n    - Make it more obvious what prodash actually is ([`5b862d5`](https://github.com/byron/prodash/commit/5b862d5e262cc08466afed235b862a6e79bffc8b))\n    - Fix compile warning ([`d48f3b9`](https://github.com/byron/prodash/commit/d48f3b97643cbd3a264400cacff29eb1436cb002))\n    - Use new resolver ([`ad03a43`](https://github.com/byron/prodash/commit/ad03a43056351899b8b76a62b0a0598b83c1e213))\n    - Fix compile warnings ([`4cb8681`](https://github.com/byron/prodash/commit/4cb8681f3826994d92b703cc7cf105ccf01cc4d8))\n    - Fix typo ([`75f311e`](https://github.com/byron/prodash/commit/75f311e5da2b5aeff608048d13075acb3dd41a0e))\n</details>\n\n## v12.0.2 (2021-01-12)\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 2 commits contributed to the release.\n - 3 days passed between releases.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - (cargo-release) version 12.0.2 ([`29e16a4`](https://github.com/byron/prodash/commit/29e16a4faa4293f160b3294d327d2f7c4083ca2c))\n    - Add all missing docs to prodash ([`473c560`](https://github.com/byron/prodash/commit/473c56048e762ca9976260598005e1508e8d579c))\n</details>\n\n## v12.0.1 (2021-01-08)\n\n* upgrade dependencies\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 10 commits contributed to the release.\n - 4 days passed between releases.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Thanks Clippy\n\n<csr-read-only-do-not-edit/>\n\n[Clippy](https://github.com/rust-lang/rust-clippy) helped 2 times to make code idiomatic. \n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - (cargo-release) version 12.0.1 ([`e93d001`](https://github.com/byron/prodash/commit/e93d00128cf24f63dcc18279bedea81d88732ef4))\n    - Prepare release ([`3e58d82`](https://github.com/byron/prodash/commit/3e58d826b855f6f10b7a787d34288f84eea35e5f))\n    - Fix localtime support ([`a3298d5`](https://github.com/byron/prodash/commit/a3298d5ab7e190d24ecdddc78f8c1cfcf5226bcb))\n    - Fix time offset calculations which can (and do) indeed fail ([`fed01f7`](https://github.com/byron/prodash/commit/fed01f7bccc234534631341706527a47d19a6dfa))\n    - Remove previous executor/reactor in favor of async-executor ([`1bea3cb`](https://github.com/byron/prodash/commit/1bea3cb16136c59bf9654fff16bcfefb1d0fa615))\n    - Use new spawn for simple example ([`9a8af0f`](https://github.com/byron/prodash/commit/9a8af0fe86246cbc99bd8440529975189e77cc00))\n    - Upgrade 'rand' ([`1c4930a`](https://github.com/byron/prodash/commit/1c4930af7e8960a94360f2ac6fdeae48920f7f93))\n    - Thanks clippy ([`644809a`](https://github.com/byron/prodash/commit/644809a7e40fc3b49116e0dfad718fc4fa850164))\n    - Upgrade dashmap and env_logger ([`cec8ab3`](https://github.com/byron/prodash/commit/cec8ab38513db79e588dc633218c565d5634f23f))\n    - Thanks clippy ([`32be130`](https://github.com/byron/prodash/commit/32be1300186fce4d543861b850ca7adf7d8c76e2))\n</details>\n\n## v12.0.0 (2021-01-04)\n\n* Upgrade to TUI v0.14\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 2 commits contributed to the release.\n - 49 days passed between releases.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Upgrade to tui 14 ([`06791b3`](https://github.com/byron/prodash/commit/06791b3cf0dd2589985bc1deb82b73e9278ae723))\n    - Update to tui 14 ([`169d62d`](https://github.com/byron/prodash/commit/169d62d04f50eb1b97e6766eb8e7a8f7878b2aef))\n</details>\n\n## v11.0.0 (2020-11-15)\n\n* Upgrade to TUI v0.13 and crossterm v0.18\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 10 commits contributed to the release over the course of 47 calendar days.\n - 59 days passed between releases.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Bump major version ([`b2989fa`](https://github.com/byron/prodash/commit/b2989faa799f27701193c52c1b335302f9751639))\n    - Upgrade to tui v0.13 ([`808f7d5`](https://github.com/byron/prodash/commit/808f7d5983ed9a3165afbd8f59959eca16d4a16a))\n    - Crosstermion now uses tui v0.13 ([`7684a50`](https://github.com/byron/prodash/commit/7684a50e1b94d6f19fa7d2c4d646ff569a51ffbd))\n    - Cargo clippy ([`725449b`](https://github.com/byron/prodash/commit/725449bfb3f4e39a543b023561b20365d9052a62))\n    - Update README.md ([`b485568`](https://github.com/byron/prodash/commit/b4855683b4d33c4db2eade4db7a33a53de47961a))\n    - Update README with accurate instructions on running example ([`d9288e9`](https://github.com/byron/prodash/commit/d9288e946bbc791f630c568b2b65899f644abd81))\n    - Ignore interrupts when reading inputs… ([`a3bf8be`](https://github.com/byron/prodash/commit/a3bf8beeb8f9496fa102ff59aeb4a7c6bdcac70e))\n    - Update to crossterm 0.18, but… ([`b5aa292`](https://github.com/byron/prodash/commit/b5aa292bd89cb223e9239bad3decc74495363b55))\n    - Update to tui 0.12 ([`a70e96d`](https://github.com/byron/prodash/commit/a70e96d64f52de0d3d4fcec9cbacbdf0fd6b7bb0))\n    - Upgrade to tui 0.12 ([`0606d46`](https://github.com/byron/prodash/commit/0606d4639c286c1917b85aee02f294dbadbaba77))\n</details>\n\n## v10.0.2 (2020-09-17)\n\n* Remove `futures-util` dependency\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 10 commits contributed to the release over the course of 3 calendar days.\n - 3 days passed between releases.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - (cargo-release) version 10.0.2 ([`6e84aef`](https://github.com/byron/prodash/commit/6e84aef4df2d5d4c465abaa9b8a8a804f238e4ac))\n    - Changelog update ([`75258ea`](https://github.com/byron/prodash/commit/75258ea37c17975b2359ee0fdf34836430a7dd08))\n    - Remove dependency to futures-util ([`45b8dba`](https://github.com/byron/prodash/commit/45b8dbafd0d7e80d7169212ee4b1a0d696e8c4ec))\n    - Switch to release version of futures-lite ([`4fda4c0`](https://github.com/byron/prodash/commit/4fda4c05e5f4b7be2ff4a3b8898b060aa5c49ed4))\n    - Switch to latest version of futures-lite to remove stream::select_all ([`53ff638`](https://github.com/byron/prodash/commit/53ff638997736010f696db21c95c70cd87792ab0))\n    - Get rid of some more future-util functionality - just one more missing ([`0c82556`](https://github.com/byron/prodash/commit/0c82556cba25c16bd50c7abc39f8bb8078ea4228))\n    - (cargo-release) version 0.3.2 ([`e187a9d`](https://github.com/byron/prodash/commit/e187a9d4addcde2587f02fdffa1d7a06a58dd3a6))\n    - Crosstermion without futures-util! ([`d41352d`](https://github.com/byron/prodash/commit/d41352dff8954cf64ed31c2644aba5c4d846256b))\n    - (cargo-release) version 0.3.1 ([`0a6b6bc`](https://github.com/byron/prodash/commit/0a6b6bcb2ce629254e715ecdff5544303e7c2d2c))\n    - Upgrade futures-lite dependency ([`da021ea`](https://github.com/byron/prodash/commit/da021ea8306eaadb2bd40b155d4c431a164b6c42))\n</details>\n\n## v10.0.1 (2020-09-13)\n\n* upgrade dependencies to latest versions\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 5 commits contributed to the release.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Thanks Clippy\n\n<csr-read-only-do-not-edit/>\n\n[Clippy](https://github.com/rust-lang/rust-clippy) helped 1 time to make code idiomatic. \n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - (cargo-release) version 10.0.1 ([`a401d55`](https://github.com/byron/prodash/commit/a401d55dffd8191308dcee55c3404d6e0d777ccd))\n    - Thanks clippy ([`2ea963c`](https://github.com/byron/prodash/commit/2ea963cb7bf31f63fa7d64e7b6183f0cb73cbcc8))\n    - Upgrade blocking ([`01db9af`](https://github.com/byron/prodash/commit/01db9af60103abc1c2dbc486943c5fe9878abb1b))\n    - Upgrade async-io ([`4d71843`](https://github.com/byron/prodash/commit/4d71843120b68fa431628627b6b5458b3576b70a))\n    - Upgrade to latest futures-lite ([`9eb47a6`](https://github.com/byron/prodash/commit/9eb47a671ea8befb7f16d805d9c7c57f0c72d5c2))\n</details>\n\n## v10.0.0 (2020-09-13)\n\n### Breaking\n\n* Enforce `Send + 'static` bounds for `Progress` trait.\n   * 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.\n   * On the call site, this avoids having to specify these trait bounds explicitly.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 1 commit contributed to the release.\n - 27 days passed between releases.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Progress with 'Send + 'static' bounds; bump major version ([`50a90ec`](https://github.com/byron/prodash/commit/50a90ece86e9642cb8005a7b1472d29b4b14f197))\n</details>\n\n## v9.0.0 (2020-08-16)\n\n### Breaking\n\n* add `set_name(…)` and `name()` to `Progress` trait.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 1 commit contributed to the release.\n - 4 days passed between releases.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Add 'name' and 'set_name' methods to trait ([`94c4390`](https://github.com/byron/prodash/commit/94c4390af8cf2ba5c9fa03d6177cff72cb762ad8))\n</details>\n\n## v8.0.1 (2020-08-11)\n\nAdd missing trailing paranthesis in throughput display\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 1 commit contributed to the release.\n - 1 day passed between releases.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Add missing parenthesis; bump patch level ([`b2af5b9`](https://github.com/byron/prodash/commit/b2af5b938d2690d2feed23e9d02ce683c280aceb))\n</details>\n\n## v8.0.0 (2020-08-10)\n\n<csr-id-66800fd4e6c9f517f19da4e26a75cb3f139353b0/>\n<csr-id-64cfe9e87e038fb36492307dfb75cbc8204180d8/>\n\n### New Features\n\n* Provide various Units for improved formatting of durations, steps, counts and bytes, and specify display of percentages.\n* Support for throughput display if enabled in _line_ and _tui_ renderer, and when opted in when creating units.\n* Turn `ProgressStep` type into usize to allow for a broader range of values. Specifically, this enables counting bytes read and written\n  of files or streams bigger than 2^32-1\n* A new example program: **units**\n* Add methods to help determine key hierarchy, useful for writing custom renderers: `Key::shares_parent_with(…)`,`Key::adjecency(…)`\n* `Key::adjecency(…)`\n\n### Breaking\n\n* In `Progress::init(max, unit)` , `unit` now is `Option<Unit>`, existing code can be transformed using `progress.init(None, Some(\"label\".into()))`.\n* moved`tree::progress` into `progress` and renamed `Value` into `Task` and `Progress` into `Value`\n* moved`tree::messages` into `crates::messages`\n* moved`tree::key` into `crates::progress::key`\n* moved `tree::Throughput` into `crate::Throughput`\n* **removed** `deep_eq()` method in `Root` tree\n* tui engine option `redraw_only_on_state_change` was removed without substitute\n* **Move and rename**\n  * `tree::ProgressState` → `progress::State`\n  * `tree::Value` → `progress::Value`\n  * `tree::Progress` → `Progress`\n* Remove `Hash` implementation for all public types except for `tree::Key`\n* Move `tui` and `line` renderers into the `render` module\n* Rename `log-renderer` feature to `progress-tree-log`\n* Rename `tui-renderer*` into `render-tui*` and `line-renderer*` into `render-line*`\n\n### Other\n\n - <csr-id-66800fd4e6c9f517f19da4e26a75cb3f139353b0/> Attempt to impl throughput in display…\n   …which can't work because it's actually never mutable due to the way\n   drawing work: it operates on a snapshot, a copy, that is not written\n   back.\n   \n   And even if it was, the type system statically concludes sync is needed\n   as well for this to work.\n   \n   Long story short: No state changes are ever allowed with a system like\n   this, and throughput needs to maintain just that.\n   \n   Throughput must be implemented in each renderer.\n - <csr-id-64cfe9e87e038fb36492307dfb75cbc8204180d8/> Try to manually implement/run a local executor\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 91 commits contributed to the release over the course of 19 calendar days.\n - 19 days passed between releases.\n - 2 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Thanks Clippy\n\n<csr-read-only-do-not-edit/>\n\n[Clippy](https://github.com/rust-lang/rust-clippy) helped 2 times to make code idiomatic. \n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - More precise throughput computation ([`360d3ee`](https://github.com/byron/prodash/commit/360d3ee664b21c126b087ada5cd897927e0389bc))\n    - Remove throughput field which was effectively constant ([`52661ee`](https://github.com/byron/prodash/commit/52661eea38a511d3b1b47d63fd9ef9ac0f46caaf))\n    - ThroughputOnDrop utility ([`a4ea34c`](https://github.com/byron/prodash/commit/a4ea34c5a0f5370e7d109abccab3872c4f38aa8d))\n    - Provide a way to take out progress from a DropOrDiscard (for completeness) ([`2d57a54`](https://github.com/byron/prodash/commit/2d57a54c1508bc9314713384fe9cb08f949cbf76))\n    - Make throughput printing more flexible, allowing to override value and unit ([`a80104c`](https://github.com/byron/prodash/commit/a80104c955675c6cc40adcd8b90885010bfc9d0a))\n    - Format throughput utility with less arguments and using available Unit ([`0e84270`](https://github.com/byron/prodash/commit/0e8427029abb2d87244aa51669ea82086b545b81))\n    - Fix benchmarks ([`7c5be6b`](https://github.com/byron/prodash/commit/7c5be6b62a85d8b06d1f2609203e38173903e611))\n    - Make 'Log' more intuitive to reach ([`8cf7452`](https://github.com/byron/prodash/commit/8cf7452b795d0d1b7e16df75cf4a1cd4c3cbec3f))\n    - Convenience functions for nicer use of render() ([`4b396f5`](https://github.com/byron/prodash/commit/4b396f5f49d879006801cd678a17628ec56f3d19))\n    - Conform feature names for renderers ([`673d149`](https://github.com/byron/prodash/commit/673d149387b745944915ae1472e7a52bbd0d48de))\n    - Conform feature name ([`8ba1e96`](https://github.com/byron/prodash/commit/8ba1e96702bccb36886fe99bb0fa5eb7c58af7b9))\n    - Thanks clipppy ([`72bfa47`](https://github.com/byron/prodash/commit/72bfa47d468b2ecaf6086b62b2d1a25c860f1baa))\n    - Log progress now works with units ([`37f1266`](https://github.com/byron/prodash/commit/37f1266131bf52b35e5c473305f4c1d07df468f2))\n    - First bare addition of log progress (from gitoxide) ([`006ba9d`](https://github.com/byron/prodash/commit/006ba9db0345953ff2b76ec4f28f9b58a60c7ec1))\n    - Feature toggle for dashmap backed tree ([`fa688c8`](https://github.com/byron/prodash/commit/fa688c80e7ba264c5b87ae06b9aadebacb0d823e))\n    - Make tui renderer use Root trait ([`3da74b0`](https://github.com/byron/prodash/commit/3da74b0aff7c615911f0259336b7ec28a11249d1))\n    - Use Root trait in line renderer ([`a977c44`](https://github.com/byron/prodash/commit/a977c446534ea7c18f037e2a51dcbc419aac7c3d))\n    - Implement Root trait for tree::Root ([`ec5c673`](https://github.com/byron/prodash/commit/ec5c6732098f9f90ba62184aacdd76f397d3d8a8))\n    - Allow general use of Throughput by moving it into the top-level ([`cdabdce`](https://github.com/byron/prodash/commit/cdabdceb7ba9db76630f0cf58a7a4e2d4cc03f6f))\n    - Move `tree::key` into `progress::key` ([`d6f66b7`](https://github.com/byron/prodash/commit/d6f66b785a12f3972c29391df8a8d842cfed2a46))\n    - Refactor ([`63fd65a`](https://github.com/byron/prodash/commit/63fd65a82c57af25023ee9615662481b49e1d4e3))\n    - First stab at sketching out the Root trait - it needs a key, however :D ([`08d30c6`](https://github.com/byron/prodash/commit/08d30c61f12aa69a66e46639016e4c9c15300a20))\n    - Upgrade `init` to support the `Unit` type ([`226a849`](https://github.com/byron/prodash/commit/226a84944c8caba5aa7abac99e3d815b33d5f935))\n    - Add `Progress` trait and implement it for `tree::Item` ([`93ffb60`](https://github.com/byron/prodash/commit/93ffb6037bec833fdf2d0bc51bbe6632d06aa17a))\n    - Rename `Value` -> `Task` and `Progress` -> `Value` ([`f021ed8`](https://github.com/byron/prodash/commit/f021ed8f26faa360bfbd68b3a4479540dcea2942))\n    - Move `tree::messages` into `crate::messages` ([`4bce1d7`](https://github.com/byron/prodash/commit/4bce1d742874dd2708f04f544d2979d8084c4667))\n    - Moved tree::progress::* back into `crate::progress` ([`cf7405c`](https://github.com/byron/prodash/commit/cf7405c3d0ef6d6cbade68f68a1615a04e34bde8))\n    - Prepare for generalizing the root interface to keep renderers general… ([`4e38c6b`](https://github.com/byron/prodash/commit/4e38c6bb1ea157fb067dd61f015e2cb0a2855c70))\n    - Move most progress related types back to tree, however… ([`e6f242e`](https://github.com/byron/prodash/commit/e6f242e8555b2ef0fc15d6cf25273a991332f47c))\n    - Move line and tui renderers into `render` module ([`31358a7`](https://github.com/byron/prodash/commit/31358a777b577772b28b9f58ba797d41fe5f4b00))\n    - Thanks clippy ([`7435f35`](https://github.com/byron/prodash/commit/7435f35f9ddd88443eb87c0fe13256172d23d0f5))\n    - Disable throughput for dashboard as it's not reqired ([`be39f49`](https://github.com/byron/prodash/commit/be39f49fa2e84139c6af2e870dcf079b234cabe6))\n    - Remove Hash from types that don't need it ([`95e89ae`](https://github.com/byron/prodash/commit/95e89ae3ac18ca0e302b84695a1b6043ecbc0ee2))\n    - Now it works! ([`2e91ed2`](https://github.com/byron/prodash/commit/2e91ed217f241290ffcb36c77e178f26393df422))\n    - First attempt to bring throughput to TUI ([`d0e3af3`](https://github.com/byron/prodash/commit/d0e3af34ebf5f01bc1923e0024667200361a104b))\n    - Refactor ([`9212de4`](https://github.com/byron/prodash/commit/9212de4662f7e35492a8796d5ef4dcece91e5ea8))\n    - Throttling throughput recomputation to 1s really does the trick ([`6a4ae4e`](https://github.com/byron/prodash/commit/6a4ae4e7b933695e4c32359237c97cacc543ea8c))\n    - First working example for throughput, but… ([`0ceebd4`](https://github.com/byron/prodash/commit/0ceebd4fa0e3b25e15cbeb3b68dddc76b28515e2))\n    - First dummy throughput impl does what it should, nice! ([`e2ffb04`](https://github.com/byron/prodash/commit/e2ffb047b2eab3785b33db0925e7503d8e6300ea))\n    - Possibly working impl of getting the throughput + reconcile ([`74315bf`](https://github.com/byron/prodash/commit/74315bf28e8451ef8008c6b98630b8984db38464))\n    - Integrate calls to (optional) throughput into line renderer ([`a1af8fc`](https://github.com/byron/prodash/commit/a1af8fc78b7e5f8fd49b28df6352e9690caed8af))\n    - First sketch of Thoughput handler ([`2edeefc`](https://github.com/byron/prodash/commit/2edeefca27592583186bca914b50e4bf6fc5f7e7))\n    - Refactor ([`80684b3`](https://github.com/byron/prodash/commit/80684b3b60b069a6942b790c9b308649f432dbc4))\n    - Refactor ([`19e0901`](https://github.com/byron/prodash/commit/19e090164be073351b16ded0a75168b48a5cf654))\n    - Refactor ([`0402040`](https://github.com/byron/prodash/commit/040204078802f1d97a1a186f75d2e61cda3dd5d5))\n    - Refactor ([`b981d2e`](https://github.com/byron/prodash/commit/b981d2eb055248ee67ada33da3b4ef946a842e8d))\n    - Make key adjecency helpers public ([`3b12ea2`](https://github.com/byron/prodash/commit/3b12ea292468ed745d54267b7635b6b67ea84195))\n    - Thanks clippy ([`a99e791`](https://github.com/byron/prodash/commit/a99e791427b5e2af4ad421545a650ca03c8f4e6c))\n    - Refactor ([`c6068c5`](https://github.com/byron/prodash/commit/c6068c58bbcbf8faa28d9dc7d5ec3766ec41d953))\n    - Refactor ([`983d2e5`](https://github.com/byron/prodash/commit/983d2e5c90773694dcd42fa54b5574a61621cf44))\n    - Refactor ([`0c537ab`](https://github.com/byron/prodash/commit/0c537ab65af9d388e02fdea7175e7fbcd4fb4a3e))\n    - Basic display of timespans for throughputs ([`fd68710`](https://github.com/byron/prodash/commit/fd687101407eced316b544dff48bb914acc5cf7f))\n    - Refactor ([`40869a8`](https://github.com/byron/prodash/commit/40869a8322faa352c0d082f7ee33c2e533d9836d))\n    - Prepare to move throughput calculations into renderer ([`682dee2`](https://github.com/byron/prodash/commit/682dee2ad35766c56e01458d0ec151587079ee4a))\n    - Attempt to impl throughput in display… ([`66800fd`](https://github.com/byron/prodash/commit/66800fd4e6c9f517f19da4e26a75cb3f139353b0))\n    - Pass elapsed time on in tui renderer ([`eb22417`](https://github.com/byron/prodash/commit/eb224176f041de36f9a6ab42888bceef0d3b85c5))\n    - Pass elapsed time on in line renderer ([`33be555`](https://github.com/byron/prodash/commit/33be555f1e6c5141c5ee756a1d42b7ea0762f975))\n    - Allow providing an optional elapsed duration… ([`a4b4ab7`](https://github.com/byron/prodash/commit/a4b4ab7d95457f7b662fb98d12eefb2bd0c34f39))\n    - First test for throughput - before major refactoring ([`963c933`](https://github.com/byron/prodash/commit/963c933ebd2f5dcb090460dc79c445e122731c9a))\n    - Remove unnecessary tui option: redraw_only_on_state_change ([`f78cf4f`](https://github.com/byron/prodash/commit/f78cf4fe8740b55f5c363151eab2ee1da558390c))\n    - Prepare throughput display ([`3266fad`](https://github.com/byron/prodash/commit/3266fad2ad5e63855eb92c1f0c390bf2bfd8167f))\n    - Make Unit a struct, introduce new 'Kind' to capture dynamic and static strings of display value ([`b413111`](https://github.com/byron/prodash/commit/b41311114ce3be7174dea7c1751fb15b20284794))\n    - Prepare Mode to carry information about throughput handling ([`ea705ac`](https://github.com/byron/prodash/commit/ea705ac4089d7958e97b81f3ee1f8261e9948d2f))\n    - Fix benches ([`a2d35fb`](https://github.com/byron/prodash/commit/a2d35fb096a915c0a55985f619a12149add29251))\n    - Finish simple units example, showing all available units ([`de6addd`](https://github.com/byron/prodash/commit/de6addde16da64a7884eca8de221d398d868171c))\n    - First simple progress bar using bytes ([`e3bdbf1`](https://github.com/byron/prodash/commit/e3bdbf1a8ef5c132405ec6570422b87ed07360ce))\n    - Frame for new example program ([`2f1cb12`](https://github.com/byron/prodash/commit/2f1cb125b455c7357fd6576f3180e145c5efdc44))\n    - Finally run unit-tests as well ([`70d7ae2`](https://github.com/byron/prodash/commit/70d7ae2bcdf98e60f83286cf12e53e0690893eef))\n    - Refactor ([`6a18584`](https://github.com/byron/prodash/commit/6a185843344c1f54efc70f8bcf0e3736b6be87b0))\n    - Use new Unit type everywhere ([`539edde`](https://github.com/byron/prodash/commit/539eddecb9d91d315771c8f30339c3b5cc186e18))\n    - Support for nicer duration display ([`d500412`](https://github.com/byron/prodash/commit/d500412766c850133569c200cc13f6040a665af2))\n    - Integrate humantime ([`f0f55bb`](https://github.com/byron/prodash/commit/f0f55bb6660523fe02cddc764a79fe2e3b459ce7))\n    - Make range more compliant to aid consistent coloring ([`afb0c91`](https://github.com/byron/prodash/commit/afb0c91a20bd187f271791056238b0563015fbd1))\n    - A new range display mode, only good with an upper bound ([`b83d6bd`](https://github.com/byron/prodash/commit/b83d6bd7137ea0dc8e9731552ae125cec6cb8631))\n    - Support for omitting the unit ([`f350079`](https://github.com/byron/prodash/commit/f350079a9db9433936f0f90fe9316d0bed65cf38))\n    - Using fmt::Write is so much better! ([`bf89a3d`](https://github.com/byron/prodash/commit/bf89a3d11fcc6eba087da1d28cace7991156bb39))\n    - Trying to use byte-size shows that the trait interface isn't flexible enough ([`f233d43`](https://github.com/byron/prodash/commit/f233d43f2d48bb911cb20808396d4a2b0d55058d))\n    - Allow splitting up generation of values and unit for more creative control… ([`0e15b97`](https://github.com/byron/prodash/commit/0e15b97d6a7cf5dcd7be068de8bf3f4b6472089c))\n    - Percentage support ([`43c0980`](https://github.com/byron/prodash/commit/43c0980ffe15efe5e4994398d16b519d9e19836e))\n    - First small steps towards implementing unit and value display correctly ([`5bd90cc`](https://github.com/byron/prodash/commit/5bd90cc6539bf61019de6d096589b1c2caeb685e))\n    - Refactor ([`716e774`](https://github.com/byron/prodash/commit/716e77489aedd9df54745793c979af67acfbb41f))\n    - Refactor in preparation for another example application ([`6063c27`](https://github.com/byron/prodash/commit/6063c2796b0b2508a73eb41b0c6c9ed8a601e21b))\n    - Rough layout on how to get much more powerful unit and value rendering ([`90a9c2d`](https://github.com/byron/prodash/commit/90a9c2dac084b5ee9a60065875401c57644dce00))\n    - First sketch for new Unit type and trait to display what we are interested in ([`129d09d`](https://github.com/byron/prodash/commit/129d09d6190619e64144a93d617b65f434d26f50))\n    - Use 'usize' as ProgressStep, instead of u32 ([`79ae31f`](https://github.com/byron/prodash/commit/79ae31fc1dde5120a6e2706bc84995f72cc587dd))\n    - Line renderer: more compact progress bar ([`8071110`](https://github.com/byron/prodash/commit/8071110e8a41612808206c3f8f444542818f4a51))\n    - Revert \"FAIL: Try to manually implement/run a local executor\" ([`f6fa7ab`](https://github.com/byron/prodash/commit/f6fa7ab681549abfd77a14e4c8015f623f720c83))\n    - Revert \"Another attempt of using a local executor: FAIL\" ([`a3254a6`](https://github.com/byron/prodash/commit/a3254a6aac6b4150bcbd10004d55d4cc06d45dbe))\n    - Another attempt of using a local executor: FAIL ([`ee6275c`](https://github.com/byron/prodash/commit/ee6275ca3f0da906b9f94e6d95d9ce130907e9c8))\n    - Try to manually implement/run a local executor ([`64cfe9e`](https://github.com/byron/prodash/commit/64cfe9e87e038fb36492307dfb75cbc8204180d8))\n    - Actually only a few lines are needed to drive multi-task ([`d19a1db`](https://github.com/byron/prodash/commit/d19a1db08305abb77505868cc237b1c71491960d))\n</details>\n\n## v7.1.1 (2020-07-22)\n\n* dependency update: smol 0.2\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 2 commits contributed to the release.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Patch bump ([`33f57ef`](https://github.com/byron/prodash/commit/33f57efcd25523318d4c494dfd29d538be964992))\n    - Upgrade to smol 2.0 ([`ab7dcb4`](https://github.com/byron/prodash/commit/ab7dcb4f2b108fbf7f525b61110f3f6b6339d0b0))\n</details>\n\n## v7.1.0 (2020-07-22)\n\n* Improved looks thanks to bold fonts\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 5 commits contributed to the release.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Bump minor version ([`aac8cb2`](https://github.com/byron/prodash/commit/aac8cb2ce99c3eb70b42919754e6ecefcab43854))\n    - Use bold characters for some highlights ([`56cf67e`](https://github.com/byron/prodash/commit/56cf67e36ed0db6a4a78e1d30e7fc4eebe7c7342))\n    - Upgrade to tui 0.10 ([`79afe12`](https://github.com/byron/prodash/commit/79afe123dda6e892425772fca8e040ac256edf9d))\n    - Crosstermion 0.3 with support for tui 0.10 ([`dcda91b`](https://github.com/byron/prodash/commit/dcda91b744cb9f4f735591a53997ac4f2747a61e))\n    - Decouple prodash from local crosstermion for tui migration ([`1105cfd`](https://github.com/byron/prodash/commit/1105cfd46d9a163c6e6d6c761fec8f70a0b08156))\n</details>\n\n## v7.0.4 (2020-07-21)\n\n* **tree::Item**\n  * Add new methods `inc_by(step)` and `inc()` for convenience \n* **line renderer**\n  * They now look clearer, as they changed from \\[===>     ] to \\[===>------]\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 7 commits contributed to the release over the course of 1 calendar day.\n - 9 days passed between releases.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - (cargo-release) version 7.0.4 ([`45a8a00`](https://github.com/byron/prodash/commit/45a8a00a76c3beed0baf0cffaa5656f0835a927e))\n    - Improve line progress rendering ([`f694bb9`](https://github.com/byron/prodash/commit/f694bb995d6b3f3ef9c25b584bbd59bf7b625c7c))\n    - Add convenience methods to tree::Item ([`3a36ce6`](https://github.com/byron/prodash/commit/3a36ce6082b3c6d24c8196ed4e9c537a5f36cb4f))\n    - Prioritize the tui engine for futures-lite ([`c17235c`](https://github.com/byron/prodash/commit/c17235c493fffb628b15a730aa72cdd39f93ca12))\n    - Attempt to switch to futures-lite, but a few things are missing ([`c8e52c2`](https://github.com/byron/prodash/commit/c8e52c2c045ddcd33f6f40d3f56ffbb3d00eb012))\n    - Remove unused dependency ([`85c1f6d`](https://github.com/byron/prodash/commit/85c1f6d894f651a006793dfa0df3a547c8ed4a29))\n    - Remove futures-util nearly completely - missing filter_map() extension for stream ([`6fc5846`](https://github.com/byron/prodash/commit/6fc58461028e63ca333adf7381178473329fe643))\n</details>\n\n## v7.0.3 (2020-07-11)\n\ncleanup and code simplification in the line renderer.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 3 commits contributed to the release.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Bump patch level ([`ffc58bb`](https://github.com/byron/prodash/commit/ffc58bbd0d305b4e3c1b33f5a6661cea4b692def))\n    - Crosstermion 0.2 with much less code and complexity ([`f3b24d0`](https://github.com/byron/prodash/commit/f3b24d0edfdc40c5fb045facd467aab2e7e53f12))\n    - Greatly simplify line renderer engine code ([`f0eaf27`](https://github.com/byron/prodash/commit/f0eaf279c42f0a7d28bc6b4c6d064df072f3a3a7))\n</details>\n\n## v7.0.2 (2020-07-11)\n\n* **render-line** `JoinHandle` will \n  * now send a signal to perform a render before shutting down to capture the final state\n  * wait for the render thread to complete the aforementioned actions on drop. You can still override this behaviour through\n   `disconnect()` or `forget()`.\n  * removed special code-paths that avoided bringing up another thread for 'ticks' at the expense of shutdown delay.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 2 commits contributed to the release.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Bump patch level ([`9a7c2ef`](https://github.com/byron/prodash/commit/9a7c2efec958c5b0729e799177a94f5089882158))\n    - Various improvements to help integrating with the line renderer ([`3711394`](https://github.com/byron/prodash/commit/371139409619f4a3195aaeabc8bf38a3b3ec6209))\n</details>\n\n## v7.0.1 (2020-07-10)\n\nPrevent cursor movement if no progress bar is drawn.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 3 commits contributed to the release.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Bump patch level ([`b1ca7fa`](https://github.com/byron/prodash/commit/b1ca7fa48d35a325566c90f49382b9385580be02))\n    - Don't cause cursor movement when using '0' as MoveUp value ([`d090c0c`](https://github.com/byron/prodash/commit/d090c0ccbce3c4323eb03dc31fc550b7c11f2cf2))\n    - Add new asciicast ([`443702b`](https://github.com/byron/prodash/commit/443702bf55c7e2677f867e1c5557dac0de78998b))\n</details>\n\n## v7.0.0 (2020-07-10)\n\n<csr-id-a684188b3eee0cc67fe48b9ae14aa9cd63603caf/>\n<csr-id-1bc5c764c9b1190f168d076b2183a27569750421/>\n\nAdd new render-line, change feature flag names.\n\n### Other\n\n - <csr-id-a684188b3eee0cc67fe48b9ae14aa9cd63603caf/> first version of 'slow' event loop which actually won't respond quickly either :D\n - <csr-id-1bc5c764c9b1190f168d076b2183a27569750421/> bump patch level\n\n### New Features\n\n* **line**\n   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\n   too dynamically changing progress trees.\n   Activate it with the `render-line` + one of `render-line-crossterm` or `render-line-termion` feature flags.\n* Activate it with the `render-line` + one of `render-line-crossterm` or `render-line-termion` feature flags.\n\n### Breaking Changes\n\n* **`tui` module**\n    * **TuiOptions** -> **Options**\n    * `render_with_input` now takes the Write stream as argument, instead of defaulting to `std::io::stdout()`\n* **Feature Flags**\n    * **with-crossterm** -> **render-tui-crossterm**\n    * **with-termion** -> **render-tui-termion**\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 81 commits contributed to the release over the course of 4 calendar days.\n - 4 days passed between releases.\n - 2 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Minior cleanup ([`48879f3`](https://github.com/byron/prodash/commit/48879f3cf44d1b8531819f7c4d3ca84d0bf116e9))\n    - Dynamically resize and align progress bars - looks so much better now! ([`021dc5a`](https://github.com/byron/prodash/commit/021dc5abda66dad0c2270e5db093b3772e56e7b3))\n    - Dynamically recompute dimensions of log messages ([`5cb7fea`](https://github.com/byron/prodash/commit/5cb7fea8e60b829ea32fec1c1a161a3cddb7cb5e))\n    - Pass entire terminal dimensions to line buffer… ([`03a859c`](https://github.com/byron/prodash/commit/03a859c09f217c0d8e39b0ba7d2f05429fd92b16))\n    - Allow setting the terminal width using CLI in dashboard example ([`bfd1a98`](https://github.com/byron/prodash/commit/bfd1a98990f9c7628bd82d6c1391dca8f783f570))\n    - Always show log messages, but show progress only after initial delay. ([`7ec5530`](https://github.com/byron/prodash/commit/7ec5530ceed8c6b32b0a82fbe0f2d5e986d57a20))\n    - Initial progress messages should actually be shown despite initial delay ([`4d68060`](https://github.com/byron/prodash/commit/4d6806028a780633e24966ca9900e0a517e6b783))\n    - More fine-grained control over the amount of work displayed ([`9f4fa49`](https://github.com/byron/prodash/commit/9f4fa4938fac88ac2b70ebb96b52ddf7152cbd04))\n    - Progress drawing now can be without color as well ([`f980a83`](https://github.com/byron/prodash/commit/f980a83226489dc1a1066a488aa39eb3c4d54cba))\n    - Release notes ([`f1280a9`](https://github.com/byron/prodash/commit/f1280a9e18d3fc530cf7eb42eeb834d986c91477))\n    - Nobody needs ticks right now, and using the step for unbounded progress rocks… ([`298afe6`](https://github.com/byron/prodash/commit/298afe685589827ab9a882f37ee53726486bd678))\n    - Ascii-only unbounded progress ([`ab93db2`](https://github.com/byron/prodash/commit/ab93db261ec994ea8874c09015a1031ed91b70a1))\n    - Refactor ([`a4aa7e8`](https://github.com/byron/prodash/commit/a4aa7e8fb894be910d0e297eb1be6c495641fd9f))\n    - Reverse direction of unbounded progress by reversing an iterator, nice! ([`380330d`](https://github.com/byron/prodash/commit/380330d1e97011bc0ca1668d4686691b4a3986be))\n    - A first, but backward, version of unbounded progress ([`89fb23c`](https://github.com/byron/prodash/commit/89fb23ca701eefa8fac9bb54697050be0ea30d28))\n    - A somewhat usable display of bounded progress ([`8ebde51`](https://github.com/byron/prodash/commit/8ebde51451792a0b45080e364db7b840bece0282))\n    - Prepare nicer but non-unicode drawing to realize we really want to configure the width ([`54e1ea7`](https://github.com/byron/prodash/commit/54e1ea77395d1da7a4bb5e91c47cc5949282780c))\n    - Overdraw for log lines - works perfectly! ([`91ea381`](https://github.com/byron/prodash/commit/91ea381f8edbe9bdf2c19a2f0fdb5d4064bc19a3))\n    - Make 'hiding the cursor' configurable ([`03a8a9e`](https://github.com/byron/prodash/commit/03a8a9ee9236a53deafdd341cd2dce18e32c05a8))\n    - Fix 'make check' ([`a7bff83`](https://github.com/byron/prodash/commit/a7bff8372416f10d0ecb4175d20cb5b08529fe13))\n    - Refactor ([`3a1b6c5`](https://github.com/byron/prodash/commit/3a1b6c5726c12a0051e255563aeb822af298f98d))\n    - Refactor ([`fb91dde`](https://github.com/byron/prodash/commit/fb91dde5cf89d3efc44d554ee1eb433d72b5d542))\n    - Show and hide the cursor if the ctrl+c is pressed (or SIG_TERM is sent) ([`ff98952`](https://github.com/byron/prodash/commit/ff98952c1f0310d32e580d5ba56d4615b64a24ed))\n    - Refactor ([`dcb24ea`](https://github.com/byron/prodash/commit/dcb24eaac1f31bb07bc3fea5a8893018c7b220b6))\n    - Use a vecdeque as it is just perfect for what we need to do here ([`8b7dbaa`](https://github.com/byron/prodash/commit/8b7dbaa9cbd2b712ed98f9af5c0ed897f95f0ebd))\n    - Basis for overdraw of log messages ([`7a4aecf`](https://github.com/byron/prodash/commit/7a4aecfef256ee4d4a21059a2ef35b010d3514e3))\n    - Obtain terminal size from crossterm or termion, now the dashboard… ([`3484fed`](https://github.com/byron/prodash/commit/3484fedd0a7b20b065edda3c101b31b2f22fd880))\n    - Support for termion version of the dashboard ([`a37cb2a`](https://github.com/byron/prodash/commit/a37cb2a03f832ee05c092c5bec0b481c1d922440))\n    - Incorporate filtering into the line count logic - that works actually ([`e38e559`](https://github.com/byron/prodash/commit/e38e55954d3bfcb016e23dac4f24b866e59aa302))\n    - Fix select issue, surprise. Crossbeam channels are certainly more robust ([`5be4885`](https://github.com/byron/prodash/commit/5be4885816d2ec2fccf2a0d2a01deb38e740edc4))\n    - Make interval ticker actually work - looks like flume can't always select ([`af710d4`](https://github.com/byron/prodash/commit/af710d4391d8103ee03dd385285159bd1d61eff3))\n    - Quite a failed attempt to move cursor back up for overdrawing… ([`04c686b`](https://github.com/byron/prodash/commit/04c686b6bec543e290b729b61ac69245953a4564))\n    - First rough drawing logic without cursor movement ([`0134e0d`](https://github.com/byron/prodash/commit/0134e0d54357d4a541555d5749e91b55ede7a692))\n    - Manually adjust fill length for correct results even with Chinese ([`4da38f2`](https://github.com/byron/prodash/commit/4da38f270c713ed2f6254154e06e655eee4dbae5))\n    - Try to align messages a bit more nicely, but… ([`2744e64`](https://github.com/byron/prodash/commit/2744e643d91ec411814df84dd755a9ffd304a9c1))\n    - Turn off timestamps by default ([`dd02770`](https://github.com/byron/prodash/commit/dd02770db79fc104b62a727cdf79266ca6f11298))\n    - Draw time as well in line renderer ([`fa51fad`](https://github.com/byron/prodash/commit/fa51fad3e0467f25d949c1dfb02cb38d10bb322f))\n    - Revert \"Add time support to crosstermion - seems a bit out of place actually\" ([`93397c7`](https://github.com/byron/prodash/commit/93397c7f89e9c3d790cf2552690de1ef66f9e2d1))\n    - Make 'time' module available to be shared by multiple renderers ([`c31d1c5`](https://github.com/byron/prodash/commit/c31d1c5adf6f83d0ffd44d91d8032f835038bb60))\n    - Add time support to crosstermion - seems a bit out of place actually ([`8e2bf7c`](https://github.com/byron/prodash/commit/8e2bf7cceb4f96d9fad523944868102951be094d))\n    - Move conditional painting code into crosstermion - could be useful for dua as well ([`6d04514`](https://github.com/byron/prodash/commit/6d04514d9015d6dcfec7688d6deab06b6ac33f54))\n    - First sketch of conditional drawing of log messages ([`78f56c0`](https://github.com/byron/prodash/commit/78f56c0ffa4bea77a6ab7458734ad2cd206ac3e8))\n    - Add a new 'color' module for crosstermion ([`272a852`](https://github.com/byron/prodash/commit/272a8525f5a102680f620d31d953467beae0b485))\n    - Fix division by zero when copying the entire message buffer ([`60f1bb8`](https://github.com/byron/prodash/commit/60f1bb8cb47b6f0720b064db811416eea52313bb))\n    - Cargo clippy ([`20f3144`](https://github.com/byron/prodash/commit/20f31449ae8e0c9de8c72f9286a7174a05972585))\n    - Add support for no-line-color flag; implement additional line renderer options ([`eda7fb3`](https://github.com/byron/prodash/commit/eda7fb32c8c621545824ecb7c37e7bfdd96cb3d3))\n    - Make coloring configurable, following some informal specs ([`1e1a02a`](https://github.com/byron/prodash/commit/1e1a02ab2c82ab332d5cbc8d5966810574cced8d))\n    - Integrate message copying into line renderer ([`79efb09`](https://github.com/byron/prodash/commit/79efb094c92bb99eae77391dd3ffc3551c79102b))\n    - Copying only new messages seems to work now ([`982dfee`](https://github.com/byron/prodash/commit/982dfeedb10d32d469fc3424b74d9629a82c3cd5))\n    - Refactor ([`3bb08f7`](https://github.com/byron/prodash/commit/3bb08f707503c802a234c18e03af7ebcd578dff4))\n    - Initial mostly working version of copying only new messages from the buffer. ([`d78472c`](https://github.com/byron/prodash/commit/d78472c579bfa20f4b2cc3fd2a062dac330dfdbf))\n    - Allow for a little more space when formatting :) ([`4bf4431`](https://github.com/byron/prodash/commit/4bf44310c9e1c37644e07092e1c5c80c9e6c45d4))\n    - First beginnings of testing copy_new(…) ([`917dd05`](https://github.com/byron/prodash/commit/917dd05122c39cd065eb5b7b761d083bc003a86f))\n    - Now actually fix the 'copy_all(…)' method for the message buffer :D ([`2bb088d`](https://github.com/byron/prodash/commit/2bb088d48408dc2106dbe5ad2bed8db5699bba15))\n    - Sketch for stateful message copying to copy only new ones. ([`90750c7`](https://github.com/byron/prodash/commit/90750c759a76d08b40b5e1b26d0d8d975afc1e2a))\n    - Fix off-by-one error in messsage buffer copy handling ([`c6e1ece`](https://github.com/byron/prodash/commit/c6e1ecefff385e4acc56047654a527316f7fd5ac))\n    - Refactor ([`ccc4297`](https://github.com/byron/prodash/commit/ccc4297a0e5dadca81a8398ae6495abbbf83cdd9))\n    - Refactor ([`ed338e0`](https://github.com/byron/prodash/commit/ed338e05c97085e0e7673d148f6d7f382a88ef03))\n    - Flesh out join handle with complete and symmetric API ([`1bd4476`](https://github.com/byron/prodash/commit/1bd4476b532425f0b7b2fdc82f058ed8b6b8317a))\n    - Support for interruptable initial delay ([`c15af5e`](https://github.com/byron/prodash/commit/c15af5ef2fe7c1bfde74f6d183c22688f666f398))\n    - Dashboard can now bring up the line renderer ([`f20f002`](https://github.com/byron/prodash/commit/f20f00282836f33465a372bf42ced1b04a3ca064))\n    - Don't assume quitting is requested on channel disconnect; allow detaching the handle ([`d050243`](https://github.com/byron/prodash/commit/d050243e8176a35ec29905a047e1a69b44ddf0e0))\n    - Frame to allow using 'line' renderer in dashboard example ([`4566e46`](https://github.com/byron/prodash/commit/4566e46155656e573af80fabd9df0ab6aec95531))\n    - Make dashboard depend on line renderer ([`ea861a2`](https://github.com/byron/prodash/commit/ea861a26e3ca8f6db37a0da79465531c2299c926))\n    - Now we are talking: Selector functions shouldn't have side-effects, 'wait()' is exactly it ([`9ebbc16`](https://github.com/byron/prodash/commit/9ebbc1685e4f7802cb6384711a06199d9cd8a58a))\n    - First version of 'slow' event loop which actually won't respond quickly either :D ([`a684188`](https://github.com/byron/prodash/commit/a684188b3eee0cc67fe48b9ae14aa9cd63603caf))\n    - Sketch draw logic for draw loops that are fast enough ([`b4db64e`](https://github.com/byron/prodash/commit/b4db64e3a134cea153db92ccb19dbc20d9ab9ee0))\n    - Sketch initial interface for line renderer ([`1712221`](https://github.com/byron/prodash/commit/1712221f04efb20717dbe662a8d29d4a23235989))\n    - Move changelog information into its own file ([`5aa3034`](https://github.com/byron/prodash/commit/5aa3034a2085fdf602641c7899448e30941183be))\n    - Prepare arrival of the mighty line-renderer feature :D ([`511389e`](https://github.com/byron/prodash/commit/511389e7491d11e06815b86f41b594a9df7c0529))\n    - Make external crates available as re-exports ([`0b8a763`](https://github.com/byron/prodash/commit/0b8a763585d7828f6ac1ccbcc23d985ff1eab3a9))\n    - Notes about the tradeoffs in backend choice ([`a942e85`](https://github.com/byron/prodash/commit/a942e85b62118c447214f683f3c866e502842337))\n    - Write down insights about coloring in terminals, in conjunction with crosstermion ([`c96abdb`](https://github.com/byron/prodash/commit/c96abdbc116801eb873e021151a853599f32ec43))\n    - Bump patch level ([`c879dfa`](https://github.com/byron/prodash/commit/c879dfa59fa4de6f7b0a02de42668224d791e5db))\n    - Fix description of crosstermion ([`8781c9f`](https://github.com/byron/prodash/commit/8781c9f3f70e12d436c957834f0a6dd61a74651b))\n    - Fix precedence in terminal module, crossterm is winning over termion now ([`521dd23`](https://github.com/byron/prodash/commit/521dd23dc67d6dee52cfbe0c35c3fe8fc28dc881))\n    - Bump patch level ([`1bc5c76`](https://github.com/byron/prodash/commit/1bc5c764c9b1190f168d076b2183a27569750421))\n    - Make sure conversions are always compiled ([`5a03628`](https://github.com/byron/prodash/commit/5a03628b1cafd8d06476f364242d0e149f4fad18))\n    - Fix flume features, bump to 0.1.2 ([`11d6665`](https://github.com/byron/prodash/commit/11d6665c77a1e4ce4dc262744ffb76dc8b907832))\n    - Bump patch level; add 'input-thread-flume' support ([`7fdbb72`](https://github.com/byron/prodash/commit/7fdbb72822c450a4e13eba998236b608cf9eeca3))\n    - Fix Cargo.toml to allow 'cargo test' to work without specifying features ([`748ab4b`](https://github.com/byron/prodash/commit/748ab4be6aa5fc975fcdcacbd92a2fa388103734))\n</details>\n\n## v6.0.0 (2020-07-05)\n\n<csr-id-bbf2651e379b5758d53a889d9fb220c616d2a096/>\n\nFactor terminal input into the new `crosstermion` crate.\n\nDue to this work, the default features changed, which is a breaking change for those who relied on it.\nNow when using the `render-tui`, one will also have to specify either the `with-crossbeam` or `render-tui-termion` feature.\n\n### Other\n\n - <csr-id-bbf2651e379b5758d53a889d9fb220c616d2a096/> Add Key input transformation\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 24 commits contributed to the release.\n - 2 days passed between releases.\n - 1 commit was understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Allow choosing the backend for the dashboard example, but people also have to chose is now ([`4841a8e`](https://github.com/byron/prodash/commit/4841a8ed3f949797990b0ad3a6148af4beb7203d))\n    - Release notes for v6.0 ([`403d5a0`](https://github.com/byron/prodash/commit/403d5a0af5fbd072b876c61d59cbaba8c261b7df))\n    - Fix crosstermion docs ([`71e1eca`](https://github.com/byron/prodash/commit/71e1eca154ccce82a012932c6b5f47cea57f4b9b))\n    - Make feature combinations for properly, but… ([`93a069a`](https://github.com/byron/prodash/commit/93a069a2e6d9757f6ca2b77300f8c7d7baf091d4))\n    - Cleanup; allow 'input-thread' and 'input-async' at the same time ([`93a30e0`](https://github.com/byron/prodash/commit/93a30e0baeeb59ea1b8f38e4a23c080c3942525f))\n    - Cleaner input handling using 'input-threaded' and 'input-async' ([`3d11e90`](https://github.com/byron/prodash/commit/3d11e9040510ccbc43057432c9e537c49fd8025d))\n    - Support for async crossterm in crosstermion ([`1a61453`](https://github.com/byron/prodash/commit/1a61453dd6e8f4c2eef7695e1e92285d318e9d9e))\n    - Use stream trait instead of channel directly to abstract a little ([`78e810d`](https://github.com/byron/prodash/commit/78e810d4ee0cb691cec3666f4c585fffd6d6481b))\n    - Don't continue the loop if sending fails… ([`e93fe6e`](https://github.com/byron/prodash/commit/e93fe6ebdf17abf38bba3e00a4c26ff822d42219))\n    - Switch from smol to futures-executor, it's probably cheaper for what we need ([`d7deeb7`](https://github.com/byron/prodash/commit/d7deeb71983d08cb5998e22f23482a0776b7fb2f))\n    - Cleanup, remove unused deps ([`050a821`](https://github.com/byron/prodash/commit/050a821a4f6d70ba624afdd48b744a62cb374254))\n    - Fix prodash build after function renmae ([`3848635`](https://github.com/byron/prodash/commit/3848635432177f5cbc7ac9f18e7c9deadfeaf588))\n    - Docs for crosstermion ([`3429327`](https://github.com/byron/prodash/commit/342932702dfc2ce359548eef72ff86d0b9030fbb))\n    - Enforce testing termion on linux ([`0cce091`](https://github.com/byron/prodash/commit/0cce091c56fcda36a1ca5eb918bb67e48db6f2d9))\n    - Prodash now uses `crosstermion` ([`97ad454`](https://github.com/byron/prodash/commit/97ad45420ad569682e3ee62d7bb7c28fe287b9e3))\n    - Bundle-features and feature documentation ([`b66c413`](https://github.com/byron/prodash/commit/b66c4133fdec9b3cfc5a290898de9ce71f22e10c))\n    - Marry raw mode with the alternative terminal, not with the creating a new tui terminal. ([`f1c734f`](https://github.com/byron/prodash/commit/f1c734f92150eff728444042b6c7cbac90989be9))\n    - Functionality for Alternate screens ([`dcc92ab`](https://github.com/byron/prodash/commit/dcc92ab05599800940359ac6c37d1e70e15c82f7))\n    - Initial terminal implementation, more generic, for tui crossterm ([`e7c07be`](https://github.com/byron/prodash/commit/e7c07be5d93feecc402d0c0793e4a3653c639651))\n    - Add everything required to build with stream support ([`2d60cc0`](https://github.com/byron/prodash/commit/2d60cc0149f80197b865312fab84575993ef5df0))\n    - Add input stream functions ([`21f4147`](https://github.com/byron/prodash/commit/21f41475e4c07539da49c370f37bd9c73611e94b))\n    - Refactor ([`42b8374`](https://github.com/byron/prodash/commit/42b83740c63a928ff3e067fef417d574b1186656))\n    - Add Key input transformation ([`bbf2651`](https://github.com/byron/prodash/commit/bbf2651e379b5758d53a889d9fb220c616d2a096))\n    - Initial version of crosstermium ([`25c8a98`](https://github.com/byron/prodash/commit/25c8a986ffb96e7c5783478c796c849a258c2ae0))\n</details>\n\n## v5.0.0 (2020-07-03)\n\nSupport for windows by using Crossbeam by default.\nA first low-effort move to the latest version should be to set the dependency to\n`default-features = false, features = [\"render-tui\", \"render-tui-termion\", \"localtime\", \"log-renderer\"]`\nto get the same configuration as before.\n\nTo try crossbeam, use `with-crossbeam` instead of `render-tui-termion`.\n\nIf you have been using the event stream to send your own keys, swap `termion::event::Key` with `prodash::tui::input::Key`.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 16 commits contributed to the release.\n - 2 days passed between releases.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Major release: windows support ([`656cb6a`](https://github.com/byron/prodash/commit/656cb6a52625ce5c6bb0e35e84f077b1c2b2243b))\n    - Make with-crossbeam the default for maximum compatibility ([`9b8be9e`](https://github.com/byron/prodash/commit/9b8be9e41db9ecf8dd6509e081aa3d26863839f6))\n    - Fix makefile check; remove termion from dev dependenices ([`aa5827d`](https://github.com/byron/prodash/commit/aa5827d5988adf931f499b95df092713ab97a2b1))\n    - Make runtime into compile time error, nice! ([`7fc5961`](https://github.com/byron/prodash/commit/7fc5961d8a7059011f0c9774e5ec10a6240df672))\n    - Refactor; actually try compiling on windows ([`52f2592`](https://github.com/byron/prodash/commit/52f2592461d6289b944d4dee339eb60cec46e0c9))\n    - Refactor ([`72fe1bb`](https://github.com/byron/prodash/commit/72fe1bbd8dcbc79424f40f47e487fb1be5f9b8b6))\n    - Refactor ([`ababea5`](https://github.com/byron/prodash/commit/ababea51c0bddf82d634fc3d6e0e7d3448d074dc))\n    - Fix crossterm input handling ([`8547c8b`](https://github.com/byron/prodash/commit/8547c8bc4489c5934653757250b2735102ea5493))\n    - First crossterm input event loop, but looks like keys are not converted properly ([`9e74ada`](https://github.com/byron/prodash/commit/9e74ada3fd730ff90110bad813fffa4f1c86a683))\n    - Implement crossterm buffer creation (alternate + raw) ([`eaf904b`](https://github.com/byron/prodash/commit/eaf904bfb1014f94bc0422ef167c671bf0b1794c))\n    - Fix accidental import ([`659065d`](https://github.com/byron/prodash/commit/659065d7f44439104dff6c8128c0ed7a4b08aca5))\n    - And map crossterm modifiers on a best-effort basis ([`bafb189`](https://github.com/byron/prodash/commit/bafb189d3d200d57edcc9c7936e3ac37fb6640a0))\n    - First stab as crossterm key event mapping ([`6650a36`](https://github.com/byron/prodash/commit/6650a368df002c2548b926cc72852541d9f3fd46))\n    - Document all tui-renderer toggles and test termion feature toggles ([`27ccdc1`](https://github.com/byron/prodash/commit/27ccdc188d4431a88ea8be0248a9dcc755c9eaa8))\n    - Allow graceful failure if no backend is chosen. ([`1f36d96`](https://github.com/byron/prodash/commit/1f36d9644983cea6c465cdc290d6c1f8d3e3b2f0))\n    - Put all usage of termion behind a feature flag and unify Key input ([`3a1dc75`](https://github.com/byron/prodash/commit/3a1dc75a0c4ca3f5b888c68de91b420f261837dd))\n</details>\n\n## v4.1.0 (2020-07-01)\n\nAllow the TUI to automatically stop if there is no progress to display.\n\nThis way, it's easier to use `prodash::tui` for visualizing finite tasks, which originally it wasn't intended for.\n\nPreviously, in order to achieve the same, one would have to initialize the TUI with an event stream and send the Event\nfor shutting down once the task at hand is complete.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 2 commits contributed to the release.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Bump minor version ([`6755f32`](https://github.com/byron/prodash/commit/6755f326f852b1cf9a8ec64d31adc7bbb3fdcaa7))\n    - Allow the TUI to automatically stop if there is no progress to display ([`4fb6079`](https://github.com/byron/prodash/commit/4fb607994b72dcf17965b0424f495ef9d0875400))\n</details>\n\n## v4.0.5 (2020-07-01)\n\nFix delayed reset of the terminal.\n\nPreviously even after the future was dropped, it seemed like the terminal wasn't reset and the user was required\nto explicitly flush stdout to make the changes appear. This is due to the flushing previously happening too early,\nthat is, before the `terminal` was dropped which emits the respective terminal escape codes at this time.\n\nNow the terminal instance is dropped explicitly right before emitting a flush.\nOne might argue that the flush should happen in the terminal instance itself, but fixing that is out of scope.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 1 commit contributed to the release.\n - 1 day passed between releases.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Bump patch level ([`f8b06a6`](https://github.com/byron/prodash/commit/f8b06a6a298928caaf2a6e6e9b2e3135d0ae443b))\n</details>\n\n## v4.0.4 (2020-06-30)\n\n- Simplify `message()` trait bounds\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 2 commits contributed to the release.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Simplify traitbounds; bump patch level ([`be4240e`](https://github.com/byron/prodash/commit/be4240e94aacdde26b58d68ce7550c3e1aaa0094))\n    - (cargo-release) start next development iteration 4.0.4-alpha.0 ([`929225d`](https://github.com/byron/prodash/commit/929225dee1e2c8936a27d127765019ecbc9ee1ad))\n</details>\n\n## v4.0.3 (2020-06-29)\n\n- Remove piper in favor of futures-channel (which was included anyway)\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 4 commits contributed to the release over the course of 33 calendar days.\n - 42 days passed between releases.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Remove async-channel in favor of (anyway imported) futures-channel ([`e4c2501`](https://github.com/byron/prodash/commit/e4c250106c2f1d81a88793c7ee74f74f21b35d68))\n    - Prepare next release ([`9879999`](https://github.com/byron/prodash/commit/9879999f66bccad3e4d43173bbaa5cc9a5126417))\n    - Update dependencies ([`73c25c2`](https://github.com/byron/prodash/commit/73c25c2ca1d95188543574ab1490961f69d001cf))\n    - Optimize include directive with 'cargo diet' ([`2978d2a`](https://github.com/byron/prodash/commit/2978d2a40b5d2a421029f7838515857cbfb45f08))\n</details>\n\n## v4.0.2 (2020-05-17)\n\n- Upgrade to latest TUI and TUI-react crates\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 1 commit contributed to the release.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Upgrade tui-* crates; bump patch level ([`7b796d1`](https://github.com/byron/prodash/commit/7b796d14cc5d2c9ceb3118e0cbfa3e3abfa86668))\n</details>\n\n## v4.0.1 (2020-05-17)\n\n- Reduce theoretical direct dependencies by not using 'futures' crate directly\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 2 commits contributed to the release.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Bump patch level ([`bf4fa4f`](https://github.com/byron/prodash/commit/bf4fa4f2fdea700f1ce254518d6791348dbe9a45))\n    - Reduce amount of dependencies by 5 ([`03970db`](https://github.com/byron/prodash/commit/03970db3d56a6ac960e95f6c67b8e4250be86791))\n</details>\n\n## v4.0.0 (2020-05-17)\n\nSwitch from futures executor to smol.\n\nThis actually simplifies some parts of the implementation, while fixing issues along futures not being dropped while they\nwere on a thread pool. Now, for the example, no threadpool is used anymore.\n\n**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\nactivate the reactor which processes the timeout/ticker. Alternatively, one would send `Tick` events through a channel to trigger\na redraw manually.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 1 commit contributed to the release.\n - 12 days passed between releases.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Replace futures-timer with smol; major version bump ([`ecb7dc8`](https://github.com/byron/prodash/commit/ecb7dc8e925c4acea08908c5c61e453d553ceec7))\n</details>\n\n## v3.6.3 (2020-05-05)\n\n- Fix out-of-bounds access (and panic) due to new and more precise progress bars\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 2 commits contributed to the release.\n - 1 day passed between releases.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Bump patch level ([`2803e46`](https://github.com/byron/prodash/commit/2803e4698bb01fea2ff06a67af7b7db5e87da564))\n    - A precaution to make out-of-bounds access impossible ([`205a0ef`](https://github.com/byron/prodash/commit/205a0ef418fa5ec645191eecdf5adfe47f0051df))\n</details>\n\n## v3.6.2 (2020-05-03)\n\n- More horizontally precise progress bars; progress bars are now have lines between them vertically\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 5 commits contributed to the release over the course of 21 calendar days.\n - 24 days passed between releases.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Bump patch level; fix crash due to integer underflow/overlow ([`af60c44`](https://github.com/byron/prodash/commit/af60c44ad27eab2cc0bdcda7b37b2df1c1176f48))\n    - More fine-grained progress bars, both horizontally and vertically ([`d6b7b3f`](https://github.com/byron/prodash/commit/d6b7b3f23215d281b8cecf64423036ddb3ae9081))\n    - Add clippy and fmt lints to actions ([`c56825c`](https://github.com/byron/prodash/commit/c56825cb46233289f8e638f3b7b941fcf09e8592))\n    - Bye bye travis, it was a good time! ([`d29fe5c`](https://github.com/byron/prodash/commit/d29fe5cbfaec8e492bda9c5a6821c88f5b227b41))\n    - Add github actions to compile and test ([`9efa70c`](https://github.com/byron/prodash/commit/9efa70c966446f9393b9495102ea892e4c3a989e))\n</details>\n\n## v3.6.1 (2020-04-09)\n\n- Properly respond to state changes even when 'redraw_only_on_state_change' is enabled\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 1 commit contributed to the release.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Patch bump; redraw also when the state changes, otherwise TUI feels laggy ([`93fce71`](https://github.com/byron/prodash/commit/93fce7193d4fc926ab0d31c868051c22b7258ece))\n</details>\n\n## v3.6.0 (2020-04-09)\n\n- A TUI option to only redraw if the progress actually changed. Useful if the change rate is lower than the frames per second.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 2 commits contributed to the release.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Bump minor ([`f209f60`](https://github.com/byron/prodash/commit/f209f6034492422550d2de24fbcecdee71a370cf))\n    - Add capability to redraw only on state change ([`5fa836f`](https://github.com/byron/prodash/commit/5fa836fa8f035e73c0e4ee3174725ef28c2a93a0))\n</details>\n\n## v3.5.1 (2020-04-09)\n\n- Don't copy messages if the message pane is hidden, saving time\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 1 commit contributed to the release.\n - 5 days passed between releases.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Bump patch level; copy messages only if the message pane is shown ([`353763b`](https://github.com/byron/prodash/commit/353763bfd588285e1e3770ea9b52a0a1fe179837))\n</details>\n\n## v3.5.0 (2020-04-03)\n\n- Cleaner visuals for hierarchical progress items, these won't show lines if there are no direct children with progress\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 3 commits contributed to the release.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Bump minor ([`39fbc16`](https://github.com/byron/prodash/commit/39fbc16a592703bd6d946a5ee5e2d1a48156042d))\n    - Don't show lines if these nodes are not actually children with progress ([`058b73a`](https://github.com/byron/prodash/commit/058b73a1f603e1538ba24172ce0715bfc0048b4a))\n    - Fix badge ([`97df7da`](https://github.com/byron/prodash/commit/97df7da752b09578aa7adad0d7c2d1866e6570f3))\n</details>\n\n## v3.4.1 (2020-04-02)\n\n- Enable localtime support by default\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 3 commits contributed to the release.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Bump minor - enable localtime by default ([`662f186`](https://github.com/byron/prodash/commit/662f186562423de7b955e24ba8432ea70ae6092b))\n    - Enable localtime by default - pulling time in isn't all that bad ([`699beba`](https://github.com/byron/prodash/commit/699bebaa9028602cd082f2b85f6531730db8e22b))\n    - Update asciinema video ([`0f07b68`](https://github.com/byron/prodash/commit/0f07b68ee60f1c69b3ea64f9d7792114fd5a293b))\n</details>\n\n## v3.4.0 (2020-04-02)\n\n- Even nicer tree rendering, along with screen space savings\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 24 commits contributed to the release over the course of 2 calendar days.\n - 2 days passed between releases.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Bump minor ([`bbdc625`](https://github.com/byron/prodash/commit/bbdc6252130c05770b6524222435e951e2ded031))\n    - Better orphan display, with dots leading up to the node's level ([`4d69c34`](https://github.com/byron/prodash/commit/4d69c3436ae0e1d5345adeff7ca8cdf575961fa5))\n    - Use space much more efficiently in the tree view as well ([`3430a8d`](https://github.com/byron/prodash/commit/3430a8d46e251b166aba70dd078ce914de845dbf))\n    - Omit line between tree on left and progress bars to safe space ([`a3e5fe5`](https://github.com/byron/prodash/commit/a3e5fe5d2489028bc764fc0e771707419176a957))\n    - Oh my, that was sooooo worth it! ([`31bb906`](https://github.com/byron/prodash/commit/31bb906c643c67d95735b22d0aba0d883425036e))\n    - Looks pretty good already, now the tweaking ([`7f16472`](https://github.com/byron/prodash/commit/7f164723dca2723c9e37fd4773820576049f4f6d))\n    - Make orphan nodes work ([`497fbce`](https://github.com/byron/prodash/commit/497fbcee0d7ceda90eafbf3800d8ca68f6da0c3a))\n    - All tests green for the first time! ([`edf2024`](https://github.com/byron/prodash/commit/edf20248be59998553a47a70a764a64fb69b482c))\n    - Maybe a tiny step closer, definitely better/fixed index handling ([`42b31da`](https://github.com/byron/prodash/commit/42b31da9262c6842243a998bb98960b5786b9b1e))\n    - Before it all goes down the drain - only one thing to fix - let's checkpoint it ([`83a89ee`](https://github.com/byron/prodash/commit/83a89ee194db870bf267bf12f6ee73daaff1502d))\n    - 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))\n    - Is this ever going to work? ([`8236d55`](https://github.com/byron/prodash/commit/8236d557eb03179e382107bc3cdb40cb1c488aba))\n    - A little closer, but needs way more tests :D ([`14876ee`](https://github.com/byron/prodash/commit/14876ee3fd5f236da9d8811096400f1728a31e3f))\n    - Refactor ([`8f6061f`](https://github.com/byron/prodash/commit/8f6061ffa75d18da2ac51d701700a90adc3b71b6))\n    - 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))\n    - Now we are actually closing in for this to work…OMG! ([`e9d268f`](https://github.com/byron/prodash/commit/e9d268f6ef670ec3c4428166d22e30c74d5eaab8))\n    - A little better ([`15a2b02`](https://github.com/byron/prodash/commit/15a2b02110135326598699733ba127a905aade2b))\n    - Add first tests for adjacency computation - it's not exactly working out of the box *:D ([`4e033b4`](https://github.com/byron/prodash/commit/4e033b4f20cca4d23ac23a3a42fd13ed50f332d5))\n    - 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))\n    - This might even work…one day :D ([`4652654`](https://github.com/byron/prodash/commit/46526549cb1392dc37552effa0837f146116d69c))\n    - Getting there, slowly, need refactor ([`46d015c`](https://github.com/byron/prodash/commit/46d015cbc3de26f54185f78fa0af8f90054f08bf))\n    - First sketch on getting an adjecency map by searching a sorted list of entries ([`0838165`](https://github.com/byron/prodash/commit/08381650f157f9430d7155d3543cb0bdfeab4864))\n    - Draw progress lines without ellipsis; make clearer that this happens ([`678fbaa`](https://github.com/byron/prodash/commit/678fbaa085ea0680f7f2212d27e6a577f65091b4))\n    - Improve tree drawing further ([`adc49c6`](https://github.com/byron/prodash/commit/adc49c624b21b6e200c4d00e29406c1262fd0c61))\n</details>\n\n## v3.3.0 (2020-03-31)\n\n- Much nicer task tree visualization\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 6 commits contributed to the release over the course of 1 calendar day.\n - 2 days passed between releases.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Bump minor ([`cf9ddb8`](https://github.com/byron/prodash/commit/cf9ddb89414a844120b60b2a5d004a411f73cf49))\n    - Much nicer display of trees, now also on the level of progress bars ([`706d658`](https://github.com/byron/prodash/commit/706d6589b23b0df3f634017f53189074997d38f5))\n    - Refactor ([`536b4c8`](https://github.com/byron/prodash/commit/536b4c878206f9c6e6fcf443bae701af628cb0d4))\n    - A nicer way to draw the progress - show the tree even in the progress bars ([`840bb3a`](https://github.com/byron/prodash/commit/840bb3a58099b13086fe325631d8858f6546252b))\n    - Much better tree display, even though the code doing it is a bit wonky :D ([`833bb98`](https://github.com/byron/prodash/commit/833bb982eb76ead49e86f021537a66a2af404b4b))\n    - Use all functionality that now is available in tui-react ([`f5b1ee1`](https://github.com/byron/prodash/commit/f5b1ee109be8000bdae1dd036d82846ceaeb905d))\n</details>\n\n## v3.2.0 (2020-03-28)\n\n- Application can control if the GUI will respond to interrupt requests\n \n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 2 commits contributed to the release.\n - 2 days passed between releases.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Bump minor; dont' discard previous value of 'interrupt requested' ([`be1ce75`](https://github.com/byron/prodash/commit/be1ce7526ba6d8abf85371f6685664390835ba01))\n    - Visualize the interrupt-requested state, while providing useful information ([`273a1a6`](https://github.com/byron/prodash/commit/273a1a62bf29a6562c246079c3deeb182cdf4ebe))\n</details>\n\n## v3.1.1 (2020-03-25)\n\n- Bugfix (really): Finally delayed column resizing works correctly.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 1 commit contributed to the release.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Bump patch - fix column sizing for good… ([`5029823`](https://github.com/byron/prodash/commit/50298236bfd32de329862540bdd00e1e1fae9fec))\n</details>\n\n## v3.1.0 (2020-03-25)\n\n- Tree::halted(…) indicates interruptable tasks without progress. Tree::blocked(…) means non-interruptable without progress.\n \n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 1 commit contributed to the release.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Support for 'halted' state to indicate interruptible tasks; bump minor ([`b5ef271`](https://github.com/byron/prodash/commit/b5ef2716b52ecf145c0aff458a05f44b0792ce7a))\n</details>\n\n## v3.0.2 (2020-03-25)\n\n- Bugfix: Allow column-width computation to recover from becoming 0\n \n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 1 commit contributed to the release.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Allow column width computation to recover from 0; bump patch ([`856b7ee`](https://github.com/byron/prodash/commit/856b7ee3206d60cb9038f06822515fba0e23688d))\n</details>\n\n## v3.0.1 (2020-03-25)\n\n<csr-id-82baf266045d44ba31aad4e570c687d7c51d0df7/>\n\n- Bugfix: Don't allow values of 0 for when to recompute task column widths\n \n\n### Other\n\n - <csr-id-82baf266045d44ba31aad4e570c687d7c51d0df7/> assure we never try to do 'x % 0' :D\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 1 commit contributed to the release.\n - 1 commit was understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Assure we never try to do 'x % 0' :D ([`82baf26`](https://github.com/byron/prodash/commit/82baf266045d44ba31aad4e570c687d7c51d0df7))\n</details>\n\n## v3.0.0 (2020-03-25)\n\n- New TUI option to delay computation of column width for stability with rapidly changing tasks\n \n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 4 commits contributed to the release.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Bump version to 3.0.0 ([`bc46acc`](https://github.com/byron/prodash/commit/bc46accd27868ad3bbb21321bee0af114d185fce))\n    - Allow to control column width recomputation ([`d4dc966`](https://github.com/byron/prodash/commit/d4dc966dc5ae1d907f7b830a03477f84baead855))\n    - Support to compute column size only every so many ticks… ([`98e0f63`](https://github.com/byron/prodash/commit/98e0f630ffe858b989e7c973de1556a4477a0c8f))\n    - Example dashboard: Make sure ticker for context does not outpace FPS ([`c25217b`](https://github.com/byron/prodash/commit/c25217b54fd23d2c450211763a16270f39c3efdb))\n</details>\n\n## v2.1.0 (2020-03-24)\n\n- Optional cargo feature \"localtime\" shows all times in the local timezone\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 2 commits contributed to the release.\n - 1 day passed between releases.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Add cargo feature 'localtime' to show all dates in localtime ([`a87f8cd`](https://github.com/byron/prodash/commit/a87f8cd0e9b9ea70a4a50b954ec3aa3160f2b3ab))\n    - Make chrono dependency available via re-exports ([`a5d4423`](https://github.com/byron/prodash/commit/a5d44233c56a9a7e05050e95c1aeaf591546bdcb))\n</details>\n\n## v2.0.1 (2020-03-23)\n\n- fix integer underflow with graphemes that report width of 0\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 2 commits contributed to the release over the course of 1 calendar day.\n - 15 days passed between releases.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Fix integer underflow with graphemes of width 0 ([`d9808cd`](https://github.com/byron/prodash/commit/d9808cd518b80fb4032a9176f1b1ec4dc5a5fdef))\n    - Transfer to new github location ([`6fa2e8d`](https://github.com/byron/prodash/commit/6fa2e8dc1b795c5614882e95e184810f7f1e466e))\n</details>\n\n## v2.0.0 (2020-03-07)\n\n* BREAKING: `progress.blocked(eta)` now takes a statically known reason for the blocked state `progress.blocked(reason, eta)`. This is\n  useful to provide more context.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 1 commit contributed to the release.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Reasons for block states; major version bump ([`74abaeb`](https://github.com/byron/prodash/commit/74abaeb4486a3a7b6889c3ed99244ab2c1b0bbf7))\n</details>\n\n## v1.2.0 (2020-03-07)\n\n* Support for eta messages in blocked unbounded tasks\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 1 commit contributed to the release.\n - 4 days passed between releases.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - ETA messages in blocked unbounded tasks ([`d42c0d5`](https://github.com/byron/prodash/commit/d42c0d52f803595a48594a91b3278ce57ff8b95b))\n</details>\n\n## v1.1.6 (2020-03-03)\n\n* improve API symmetry by providing a `Tree::name()` to accompany `Tree::set_name(…)`\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 2 commits contributed to the release over the course of 5 calendar days.\n - 8 days passed between releases.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Improve API symmetry ([`759f1f4`](https://github.com/byron/prodash/commit/759f1f4164f21804336f2c5677322b59c4218f7a))\n    - Put changelog into lib.rs, where it already was :) ([`dfc810b`](https://github.com/byron/prodash/commit/dfc810b7b012e536353ceeed61f2ca7ed935930d))\n</details>\n\n## v1.1.5 (2020-02-24)\n\n* Flush stdout when the TUI stopped running. That way, the alternate/original screen will be shown right away.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 1 commit contributed to the release.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - V1.1.5 - flush stdout right away ([`8734668`](https://github.com/byron/prodash/commit/87346682d74951ed332a2533c00232095cc1d40b))\n</details>\n\n## v1.1.4 (2020-02-23)\n\n* Don't pretend to use &str if in fact an owned string is required. This caused unnecessary clones for those who pass owned strings.\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 3 commits contributed to the release.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Version bump ([`15d9681`](https://github.com/byron/prodash/commit/15d9681acd734c8518a541f621ccc69c24d9a0f3))\n    - Cargo clippy ([`3adba17`](https://github.com/byron/prodash/commit/3adba17c71b8e98634cf651219ce247b2182174e))\n    - Don't pretend we only need str ([`d05158b`](https://github.com/byron/prodash/commit/d05158b135bab195dc8a34a573cee120079146b2))\n</details>\n\n## v1.1.3 (2020-02-23)\n\n* hide cursor or a nicer visual experience\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 3 commits contributed to the release.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Hide cursor, bump version ([`ecf5cc4`](https://github.com/byron/prodash/commit/ecf5cc4a55536d36290b8bea1f816d3d29bb5468))\n    - Run the few doc tests we have ([`9b28cc0`](https://github.com/byron/prodash/commit/9b28cc0fce171bbadf736d25d8aec1614f24caf7))\n    - (cargo-release) start next development iteration 1.1.3-alpha.0 ([`cae0e02`](https://github.com/byron/prodash/commit/cae0e024d017d84962dc459e200d361c372f9c89))\n</details>\n\n## v1.1.2 (2020-02-22)\n\n### Commit Statistics\n\n<csr-read-only-do-not-edit/>\n\n - 5 commits contributed to the release.\n - 0 commits were understood as [conventional](https://www.conventionalcommits.org).\n - 0 issues like '(#ID)' were seen in commit messages\n\n### Commit Details\n\n<csr-read-only-do-not-edit/>\n\n<details><summary>view details</summary>\n\n * **Uncategorized**\n    - Show travis badge ([`65b48f1`](https://github.com/byron/prodash/commit/65b48f1270310453e7b03363ddfdad5551aa6900))\n    - Bump version for new crate meta-data ([`c557b6b`](https://github.com/byron/prodash/commit/c557b6b05206822e2b01feb079d2b8745b408019))\n    - Travis support ([`507cc20`](https://github.com/byron/prodash/commit/507cc207742b536a0bd92a4357f6cfc1b5b2126b))\n    - Adjust repository link ([`d75d91f`](https://github.com/byron/prodash/commit/d75d91fe16e32747bb6400cbe3b3e63c4399f123))\n    - Initial commit, as copied from cli ([`708901b`](https://github.com/byron/prodash/commit/708901b796110bd49bcac6bc1ef1daa7b1931720))\n</details>\n\n## v1.1.0\n\n* fix toggles - previously prodash, withoug tui, would always build humantime and unicode width\n* add support for logging as user interface\n\n## v0.7.0 (2021-05-02)\n\n## v0.6.0 (2021-01-04)\n\n## v0.5.0 (2020-11-15)\n\n## v0.4.0 (2020-09-28)\n\n## v0.3.2 (2020-09-14)\n\n## v0.3.1 (2020-09-13)\n\n## v0.3.0 (2020-07-22)\n\n## v0.2.0 (2020-07-11)\n\n## v0.1.4 (2020-07-06)\n\n## v0.1.3 (2020-07-06)\n\n## v0.1.2 (2020-07-06)\n\n## v0.1.1 (2020-07-05)\n\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"prodash\"\nversion = \"31.0.0\"\nauthors = [\"Sebastian Thiel <sebastian.thiel@icloud.com>\"]\ndescription = \"A dashboard for visualizing progress of asynchronous and possibly blocking tasks\"\nedition = \"2021\"\ninclude = [\"src/**/*\", \"README.md\", \"LICENSE.md\", \"CHANGELOG.md\"]\nlicense = \"MIT\"\nrepository = \"https://github.com/GitoxideLabs/prodash\"\nreadme = \"README.md\"\nrust-version = \"1.74\"\n\n[lib]\ndoctest = true\n\n[[example]]\nname = \"dashboard\"\npath = \"examples/dashboard.rs\"\nrequired-features = [\"render-tui\", \"render-tui-crossterm\", \"render-line\", \"render-line-crossterm\", \"signal-hook\", \"render-line-autoconfigure\", \"progress-tree\"]\n\n[[example]]\nname = \"units\"\npath = \"examples/units.rs\"\nrequired-features = [\n    \"unit-bytes\",\n    \"unit-duration\",\n    \"unit-human\",\n    \"render-tui\",\n    \"render-tui-crossterm\",\n    \"render-line\",\n    \"render-line-crossterm\",\n    \"signal-hook\"\n]\n\n[features]\ndefault = [\"progress-tree\"]\nprogress-tree = [\"parking_lot\"]\nprogress-tree-hp-hashmap = [\"dashmap\"]\nprogress-tree-log = [\"log\"]\nprogress-log = [\"log\"]\nunit-bytes = [\"bytesize\"]\nunit-human = [\"human_format\"]\nunit-duration = [\"jiff\"]\nrender-tui-crossterm = [\"crosstermion/tui-react-crossterm\", \"crosstermion/input-async-crossterm\"]\nrender-tui = [\"tui\",\n    \"unicode-segmentation\",\n    \"unicode-width\",\n    \"crosstermion/input-async\",\n    \"tui-react\",\n    \"futures-lite\",\n    \"futures-core\",\n    \"async-io\",\n    \"jiff\"]\nrender-line = [\"crosstermion/color\", \"jiff\", \"unicode-width\"]\nrender-line-crossterm = [\"crosstermion/crossterm\"]\nrender-line-autoconfigure = [\"is-terminal\"]\n\nlocal-time = [\"jiff\"]\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n[dependencies]\ndashmap = { version = \"6.0.1\", optional = true, default-features = false }\nparking_lot = { version = \"0.12.1\", optional = true, default-features = false }\n\n# with-logging\nlog = { version = \"0.4.8\", optional = true }\n\n# render-tui\ntui = { package = \"ratatui\", version = \"0.30.0\", optional = true, default-features = false }\ntui-react = { version = \"0.24.0\", optional = true }\nfutures-core = { version = \"0.3.32\", optional = true, default-features = false }\nfutures-lite = { version = \"2.1.0\", optional = true }\nunicode-segmentation = { version = \"1.6.0\", optional = true }\nunicode-width = { version = \"0.2.2\", optional = true }\ncrosstermion = { version = \"0.16.0\", optional = true, default-features = false }\nasync-io = { version = \"2.2.1\", optional = true }\n\n# localtime support for render-tui and duration formatting\njiff = { version = \"0.2.23\", optional = true }\n\n# line renderer\nctrlc = { version = \"3.5.2\", optional = true, default-features = false, features = ['termination'] }\nsignal-hook = { version = \"0.4.3\", optional = true, default-features = false }\nis-terminal = { version = \"0.4.9\", optional = true }\n\n# units\nbytesize = { version = \"2.0.1\", optional = true }\nhuman_format = { version = \"1.2.1\", optional = true }\n\n[package.metadata.docs.rs]\nall-features = true\n\n[dev-dependencies]\nrand = \"0.10.1\"\nenv_logger = { version = \"0.11.10\", default-features = false, features = [\"humantime\"] }\ncriterion = { version = \"0.8.2\", default-features = false }\nfutures-util = { version = \"0.3.32\", default-features = false }\nargh = \"0.1.19\"\nfutures = \"0.3.32\"\nis-terminal = \"0.4.9\"\nblocking = \"1.0.0\"\nonce_cell = \"1.21.4\"\nasync-executor = \"1.14.0\"\nasync-io = \"2.2.1\"\n\n[[bench]]\nname = \"usage\"\npath = \"benches/usage.rs\"\nharness = false\n"
  },
  {
    "path": "LICENSE.md",
    "content": "The MIT License (MIT)\n=====================\n\nCopyright © `2020` `Sebastian Thiel`\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of this software and associated documentation\nfiles (the “Software”), to deal in the Software without\nrestriction, including without limitation the rights to use,\ncopy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the\nSoftware is furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\nOF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\nHOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\nWHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": ".PHONY : tests build\n\nhelp:  ## Display this help\n\t@awk 'BEGIN {FS = \":.*##\"; printf \"\\nUsage:\\n  make \\033[36m<target>\\033[0m\\n\"} /^[a-zA-Z_-]+:.*?##/ { printf \"  \\033[36m%-15s\\033[0m %s\\n\", $$1, $$2 } /^##@/ { printf \"\\n\\033[1m%s\\033[0m\\n\", substr($$0, 5) } ' $(MAKEFILE_LIST)\n\n##@ Testing\n\nclippy: ## Run cargo-clippy\n\tcargo clippy --all-features\n\ncheck: ## build features in commmon combination to be sure it all stays together\n\tcargo check --all-features\n\tcargo check --no-default-features\n\tcargo check --features progress-tree,progress-tree-hp-hashmap\n\tcargo check --features render-tui,render-tui-crossterm\n\tcargo check --features render-line,render-line-crossterm\n\tcargo check --features render-line,render-line-crossterm,render-tui,render-tui-crossterm,signal-hook,render-line-autoconfigure --example dashboard\n\tcargo check --features unit-bytes,unit-duration,unit-human,render-tui,render-tui-crossterm,render-line,render-line-crossterm,signal-hook --example units\n\tcargo check\n\nunit-test: ## Run all unit tests\n\tcargo test --features unit-bytes,unit-human,unit-duration\n\ntests: clippy check unit-test ## Run all tests we have\n\nbench: ## Run criterion based benchmark, works on stable Rust\n\tcargo bench\n\nbench-ci: ## Just build the criterion based benchmark, avoid running.\n\tcargo bench --no-run\n\n##@ Development\n\nfmt: ## run nightly rustfmt for its extra features, but check that it won't upset stable rustfmt\n\tcargo +nightly fmt --all -- --config-path rustfmt-nightly.toml\n\tcargo +stable fmt --all -- --check\n\n\n"
  },
  {
    "path": "README.md",
    "content": "[![CI](https://github.com/GitoxideLabs/prodash/actions/workflows/ci.yml/badge.svg)](https://github.com/GitoxideLabs/prodash/actions)\n[![crates.io](https://img.shields.io/crates/v/prodash.svg)](https://crates.io/crates/prodash)\n\n**prodash** allows to integrate progress reporting into concurrent applications and provides renderers for displaying it in various ways.\n\nIt's easy to integrate thanks to a pragmatic API, and comes with a terminal user interface by default.\n\n[![asciicast](https://asciinema.org/a/315956.svg)](https://asciinema.org/a/315956)\n[![asciicast](https://asciinema.org/a/346619.svg)](https://asciinema.org/a/346619)\n\n## How to use…\n\nBe sure to read the documentation at https://docs.rs/prodash, it contains various examples on how to get started.\n\nOr run the demo application like so `cd prodash && cargo run --all-features --example dashboard`.\n\n## Feature Toggles\n\nThis crate comes with various cargo features to tailor it to your needs.\n\n* **progress-tree** _(default)_\n  * Provide a `Progress` and `Root` trait implementation for use with the `render-line` and `render-tui` backed by `dashmap`.\n  * **progress-tree-hp-hashmap** - high-performance registry for pregree tree nodes in case of ultra-heavy insertions and deletions.\n    * If this is necessary, it's probably impossible to resonably visualize the progress tree anyway, but the option exists nonetheless in case\n      it is ever needed. Historically, this was the default, but now it seems simpler is better and just fine for typical programs.\n  * **progress-tree-log**\n    * If logging in the `log` crate is initialized, a `log` will be used to output all messages provided to\n      `tree::Item::message(…)` and friends. No actual progress is written.\n    * May interfere with `render-tui` or `render-line`, or any renderer outputting to the console.\n* **progress-log**\n  * A `Progress` implementation which logs messages and progress using the `log` crate\n* **local-time**\n  * If set, timestamps in the message pane of the `render-tui` will be using the local time, not UTC\n  * If set, timestamps of the log messages of the `render-line` will be using the local time, not UTC\n  * Has no effect without the `render-tui` or `render-line` respectively\n* **render-line**\n  * Provide a minimal line-based progress renderer which can be limited to a subset of the progress hierarchy.\n  * It's like the render-tui, but with far less dependencies and less visual fidelity - all it needs is to move\n    the cursor a little while drawing characters and block graphics.\n  * Support for [clicolors spec](https://bixense.com/clicolors/) and [no-color spec](https://no-color.org)\n  * Supports initial delay that won't affect log messages, showing progress only when needed, automatically.\n  * Requires one of these additional feature flags to be set to be functional\n    * **one required** _(mutually exclusive)_\n       * **render-line-crossterm** - use the _crossterm_ backend, useful for working on windows\n       * **render-line-termion** - use the _termion_ backend, useful for lean unix-only builds\n  * _Optional features_\n       * **render-line-autoconfigure**\n         * If enabled, calls to `render::line::Options::auto_configure()` will configure the display based on whether or not we are in a terminal\n           and set its color mode based on what's possible or desired.\n       * **signal-hook**\n          * 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\n            installed to reset the cursor on exit. Otherwise you have to make sure to call `shutdown_and_wait()` on the `JoinHandle` returned\n            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\n            finished.\n          * Comes at the cost of an extra thread and additional dependencies.\n* **render-tui**\n  * Provide a terminal user interface visualizing every detail of the current progress state. It treats the terminal\n    as a matrix display.\n  * Requires one of these additional feature flags to be set to be functional\n    ** _(one required, mutually exclusive)_\n       * **render-tui-crossterm**\n         * Use the `crossterm` crate as terminal backend\n         * Works everywhere natively, but has more dependencies\n         * You can set additional features like this `cargo build --features render-tui-crossterm,crossterm/event-stream`\n       * **render-tui-termion**\n         * Use the `termion` crate as terminal backend\n         * It has less dependencies but works only on `unix` systems\n         * to get this, disable default features and chose at least `render-tui` and `render-tui-termion`.\n* **unit-bytes**\n  * Supports dynamic byte display using the tiny `bytesize` crate.\n* **unit-human**\n  * Display counts in a way that is easier to grasp for humans, using the tiny `human_format` crate.\n* **unit-duration**\n  * Displays time in seconds like '_5m4s_' using the `jiff` crate's friendly duration format.\n\n## Features\n\n* fast insertions and updates for transparent progress tracking of highly concurrent programs\n* a messages buffer for information about success and failure\n* a terminal user interface for visualization, with keyboard controls and dynamic re-sizing\n* unicode and multi-width character support\n\n## Limitations\n\n* the *line renderer* is inherently limited in the amount of progress it can display without visual artifacts.\n* it does copy quite some state each time it displays progress information and messages\n* The underlying sync data structure, `dashmap`, does not document every use of unsafe\n  * I also evaluated `evmap`, which has 25% less uses of unsafe, but a more complex interface.\n  * Thus far it seemed 'ok' to use, who knows… we are getting mutable pieces of a hashmap from multiple threads,\n    however, we never hand out multiple handles to the same child which should make actual concurrent access to\n    the same key impossible.\n* If there are more than 2^16 tasks\n  * then\n    * running concurrently on a single level of the tree, they start overwriting each other\n    * over its lifetime, even though they do not run concurrently, eventually new tasks will seem like old tasks (their ID wrapped around)\n  * why\n    * on drop, they never decrement a child count used to generate a new ID\n  * fix\n    * make the id bigger, like u32\n    * we should do that once there is a performance test\n* If the log lines are too long for the terminal width when using the *line renderer*\n  * then\n    * visual artifacts will appear\n  * why\n    * trying to draw beyond the terminal boundary will add a line break automatically, which can cause unexpected overdraw.\n  * **fix**\n    * count amount of blocks drawn, without ansi codes, and stop drawing at the boundary.\n\n## Lessons Learned\n\n* `drop()` is not garantueed to be called when the future returns Ready and is in the futures::executor::ThreadPool\n  * Workaround: drop and cleanup explicitly, prone to forgetting it.\n  * This is also why `futures::future::abortable()` works (by stopping the polling), but doesn't as cleanup is not performed,\n    even though it clearly would be preferred.\n  * fix\n    * Use a join handle and await it - this will drop the future properly\n* `select()` might not work with complex futures - these should then be `boxed()` if `Unpin` isn't implemented.\n\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Reporting a Vulnerability\n\nPlease feel free to [draft a GitHub advisory](https://github.com/GitoxideLabs/prodash/security/advisories/new), and I will work with you to disclose and or resolve the issue responsibly.\n\nIf this doesn't seem like the right approach or there are questions, please feel free to reach out to the email used in Sebastian Thiel's commits.\n\nThank you.\n"
  },
  {
    "path": "benches/usage.rs",
    "content": "use criterion::*;\nuse prodash::{\n    messages::MessageLevel,\n    tree::{root::Options as TreeOptions, Root as Tree},\n    BoxedDynNestedProgress, Count,\n};\nuse std::sync::atomic::Ordering;\n\nfn usage(c: &mut Criterion) {\n    fn small_tree() -> std::sync::Arc<Tree> {\n        TreeOptions {\n            initial_capacity: 10,\n            message_buffer_capacity: 2,\n        }\n        .create()\n        .into()\n    }\n    c.benchmark_group(\"Shared Counter\")\n        .throughput(Throughput::Elements(5))\n        .bench_function(\"inc counter 5 times\", |b| {\n            let root = small_tree();\n            let progress = root.add_child(\"the one\");\n            progress.init(Some(20), Some(\"element\".into()));\n            let counter = progress.counter();\n            b.iter(|| {\n                counter.fetch_add(1, Ordering::Relaxed);\n                counter.fetch_add(1, Ordering::Relaxed);\n                counter.fetch_add(1, Ordering::Relaxed);\n                counter.fetch_add(1, Ordering::Relaxed);\n                counter.fetch_add(1, Ordering::Relaxed);\n            });\n        })\n        .bench_function(\"set counter 5 times\", |b| {\n            let root = small_tree();\n            let progress = root.add_child(\"the one\");\n            progress.init(Some(20), Some(\"element\".into()));\n            let counter = progress.counter();\n            b.iter(|| {\n                counter.store(1, Ordering::SeqCst);\n                counter.store(2, Ordering::SeqCst);\n                counter.store(3, Ordering::SeqCst);\n                counter.store(4, Ordering::SeqCst);\n                counter.store(5, Ordering::SeqCst);\n            });\n        });\n    c.benchmark_group(\"BoxedDynProgress\")\n        .throughput(Throughput::Elements(5))\n        .bench_function(\"set tree 5 times\", |b| {\n            let root = small_tree();\n            let progress = root.add_child(\"the one\");\n            progress.init(Some(20), Some(\"element\".into()));\n            let progress = BoxedDynNestedProgress::new(progress);\n            b.iter(|| {\n                progress.set(1);\n                progress.set(2);\n                progress.set(3);\n                progress.set(4);\n                progress.set(5);\n            });\n        })\n        .bench_function(\"inc tree 5 times\", |b| {\n            let root = small_tree();\n            let progress = root.add_child(\"the one\");\n            progress.init(Some(20), Some(\"element\".into()));\n            let progress = BoxedDynNestedProgress::new(progress);\n            b.iter(|| {\n                progress.inc();\n                progress.inc();\n                progress.inc();\n                progress.inc();\n                progress.inc();\n            });\n        });\n    c.benchmark_group(\"tree::Item\")\n        .throughput(Throughput::Elements(5))\n        .bench_function(\"set tree 5 times\", |b| {\n            let root = small_tree();\n            let progress = root.add_child(\"the one\");\n            progress.init(Some(20), Some(\"element\".into()));\n            b.iter(|| {\n                progress.set(1);\n                progress.set(2);\n                progress.set(3);\n                progress.set(4);\n                progress.set(5);\n            });\n        })\n        .bench_function(\"inc tree 5 times\", |b| {\n            let root = small_tree();\n            let progress = root.add_child(\"the one\");\n            progress.init(Some(20), Some(\"element\".into()));\n            b.iter(|| {\n                progress.inc();\n                progress.inc();\n                progress.inc();\n                progress.inc();\n                progress.inc();\n            });\n        });\n    c.benchmark_group(\"Tree::add_child\")\n        .throughput(Throughput::Elements(4))\n        .bench_function(\"add children to build a tree of tasks and clear them (in drop)\", |b| {\n            let root = small_tree();\n            b.iter(|| {\n                let mut c = root.add_child(\"1\");\n                let _one = c.add_child(\"1\");\n                let _two = c.add_child(\"2\");\n                let _three = c.add_child(\"3\");\n            });\n        });\n    c.benchmark_group(\"tree::Item::message\")\n        .throughput(Throughput::Elements(1))\n        .bench_function(\n            \"send one message with a full message buffer (worst case performance)\",\n            |b| {\n                let root = small_tree();\n                let progress = root.add_child(\"the one\");\n                progress.init(Some(20), Some(\"element\".into()));\n                b.iter(|| {\n                    progress.message(MessageLevel::Success, \"for testing\");\n                });\n            },\n        );\n    c.benchmark_group(\"Tree::copy_messages\")\n        .throughput(Throughput::Elements(2))\n        .bench_function(\"copy all messages with buffer being at capacity\", |b| {\n            let root = small_tree();\n            let mut progress = root.add_child(\"the one\");\n            progress.init(Some(20), Some(\"element\".into()));\n            progress.done(\"foo\");\n            progress.done(\"bar\");\n            let mut out = Vec::new();\n            b.iter(|| {\n                root.copy_messages(&mut out);\n            });\n        });\n}\n\ncriterion_group!(benches, usage);\ncriterion_main!(benches);\n"
  },
  {
    "path": "examples/dashboard.rs",
    "content": "#![deny(unsafe_code)]\n\n#[cfg(not(feature = \"render-tui\"))]\ncompile_error!(\n    \"The `render-tui` feature must be set, along with either `render-tui-crossterm` or `render-tui-termion`\"\n);\n#[cfg(not(any(feature = \"render-tui-crossterm\")))]\ncompile_error!(\"Please set the 'render-tui-crossterm' feature when using the 'render-tui'\");\n\nfn main() -> Result {\n    env_logger::init();\n\n    let args: args::Options = argh::from_env();\n    futures_lite::future::block_on(work_forever(args))\n}\n\nasync fn work_forever(mut args: args::Options) -> Result {\n    let progress: Arc<_> = prodash::tree::root::Options {\n        message_buffer_capacity: args.message_scrollback_buffer_size,\n        ..Default::default()\n    }\n    .create()\n    .into();\n    {\n        let mut sp = progress.add_child(\"preparation\");\n        sp.info(\"warming up\");\n        spawn(async move {\n            async_io::Timer::after(Duration::from_millis(500)).await;\n            sp.fail(\"engine failure\");\n            async_io::Timer::after(Duration::from_millis(750)).await;\n            sp.done(\"warmup complete\");\n        })\n        .detach();\n    }\n    // Now we should handle signals to be able to cleanup properly\n    let speed = args.speed_multitplier;\n    let changing_names = args.changing_names;\n\n    let renderer = args.renderer.take().unwrap_or_else(|| \"tui\".into());\n    let work_min = args.pooled_work_min;\n    let work_max = args.pooled_work_max;\n    let mut gui_handle = if renderer == \"log\" {\n        let never_ending = spawn(futures_lite::future::pending::<()>());\n        Some(never_ending.boxed())\n    } else {\n        Some(\n            shared::launch_ambient_gui(progress.clone(), &renderer, args, false)\n                .unwrap()\n                .boxed(),\n        )\n    };\n\n    loop {\n        let local_work = new_chunk_of_work(\n            NestingLevel(rng().random_range(0..=Key::max_level())),\n            progress.clone(),\n            speed,\n            changing_names,\n        )\n        .boxed_local();\n        let num_chunks = if work_min < work_max {\n            rng().random_range(work_min..=work_max)\n        } else {\n            work_min\n        };\n        let pooled_work = (0..num_chunks).map(|_| {\n            spawn(new_chunk_of_work(\n                NestingLevel(rng().random_range(0..=Key::max_level())),\n                progress.clone(),\n                speed,\n                changing_names,\n            ))\n            .boxed_local()\n        });\n\n        match futures_util::future::select(\n            join_all(std::iter::once(local_work).chain(pooled_work)),\n            gui_handle.take().expect(\"gui handle\"),\n        )\n        .await\n        {\n            Either::Left((_workblock_result, running_gui)) => {\n                gui_handle = Some(running_gui);\n                continue;\n            }\n            Either::Right(_gui_shutdown) => break,\n        }\n    }\n\n    if let Some(gui) = gui_handle {\n        // gui.cancel();\n        gui.await;\n    }\n    Ok(())\n}\n\nasync fn work_item(mut progress: Item, speed: f32, changing_names: bool) {\n    let max: u8 = rng().random_range(25..=125);\n    progress.init(\n        if max > WORK_STEPS_NEEDED_FOR_UNBOUNDED_TASK {\n            None\n        } else {\n            Some(max.into())\n        },\n        if (max as usize % UNITS.len() + 1) == 0 {\n            None\n        } else {\n            UNITS.choose(&mut rng()).copied().map(Into::into)\n        },\n    );\n\n    for step in 0..max {\n        progress.set(step as Step);\n        let delay_ms = if rng().random_bool(CHANCE_TO_BLOCK_PER_STEP) {\n            let eta = if rng().random_bool(CHANCE_TO_SHOW_ETA) {\n                Some(SystemTime::now().add(Duration::from_millis(LONG_WORK_DELAY_MS)))\n            } else {\n                None\n            };\n            if rng().random_bool(0.5) {\n                progress.halted(REASONS.choose(&mut rng()).unwrap(), eta);\n            } else {\n                progress.blocked(REASONS.choose(&mut rng()).unwrap(), eta);\n            }\n            rng().random_range(WORK_DELAY_MS..=LONG_WORK_DELAY_MS)\n        } else {\n            rng().random_range(SHORT_DELAY_MS..=WORK_DELAY_MS)\n        };\n        if rng().random_bool(0.01) {\n            progress.init(Some(max.into()), UNITS.choose(&mut rng()).copied().map(Into::into))\n        }\n        if rng().random_bool(0.01) {\n            progress.info(*INFO_MESSAGES.choose(&mut rng()).unwrap());\n        }\n        if rng().random_bool(if changing_names { 0.5 } else { 0.01 }) {\n            progress.set_name(WORK_NAMES.choose(&mut rng()).unwrap().to_string());\n        }\n        async_io::Timer::after(Duration::from_millis((delay_ms as f32 / speed) as u64)).await;\n    }\n    if rng().random_bool(0.95) {\n        progress.done(*DONE_MESSAGES.choose(&mut rng()).unwrap());\n    } else {\n        progress.fail(*FAIL_MESSAGES.choose(&mut rng()).unwrap());\n    }\n}\n\nasync fn new_chunk_of_work(max: NestingLevel, tree: Arc<Tree>, speed: f32, changing_names: bool) -> Result {\n    let NestingLevel(max_level) = max;\n    let mut progresses = Vec::new();\n    let mut level_progress = tree.add_child(format!(\"level {} of {}\", 1, max_level));\n    let mut handles = Vec::new();\n\n    for level in 0..max_level {\n        // one-off ambient tasks\n        let num_tasks = max_level as usize * 2;\n        for id in 0..num_tasks {\n            let handle = spawn(work_item(\n                level_progress.add_child(format!(\"{} {}\", WORK_NAMES.choose(&mut rng()).unwrap(), id + 1)),\n                speed,\n                changing_names,\n            ));\n            handles.push(handle);\n\n            async_io::Timer::after(Duration::from_millis((SPAWN_DELAY_MS as f32 / speed) as u64)).await;\n        }\n        if level + 1 != max_level {\n            let tmp = level_progress.add_child(format!(\"Level {}\", level + 1));\n            progresses.push(level_progress);\n            level_progress = tmp;\n        }\n    }\n\n    progresses.push(level_progress);\n    for handle in handles.into_iter() {\n        handle.await;\n    }\n\n    Ok(())\n}\n\nstruct NestingLevel(u8);\ntype Result = std::result::Result<(), Box<dyn Error + Send>>;\n\nuse std::{\n    error::Error,\n    ops::Add,\n    sync::Arc,\n    time::{Duration, SystemTime},\n};\n\nuse futures_util::{\n    future::{join_all, Either},\n    FutureExt,\n};\nuse prodash::{\n    progress::{Key, Step},\n    tree::{Item, Root as Tree},\n};\nuse rand::{prelude::IndexedRandom, rng, RngExt};\n\nconst WORK_STEPS_NEEDED_FOR_UNBOUNDED_TASK: u8 = 100;\nconst UNITS: &[&str] = &[\"Mb\", \"kb\", \"items\", \"files\"];\nconst REASONS: &[&str] = &[\"due to star alignment\", \"IO takes time\", \"仪表板演示\", \"just because\"];\nconst WORK_NAMES: &[&str] = &[\n    \"Downloading Crate\",\n    \"下载板条箱\",\n    \"Running 'cargo geiger'\",\n    \"运行程序 'cargo geiger'\",\n    \"Counting lines of code\",\n    \"计数代码行\",\n    \"Checking for unused dependencies\",\n    \"检查未使用的依赖项\",\n    \"Checking for crate-bloat\",\n    \"检查板条箱膨胀\",\n    \"Generating report\",\n    \"生成报告\",\n];\nconst DONE_MESSAGES: &[&str] = &[\n    \"Yeeeehaa! Finally!!\",\n    \"呀！ 最后！\",\n    \"It feels good to be done!\",\n    \"感觉好极了！\",\n    \"Told you so!!\",\n    \"告诉过你了！\",\n];\nconst FAIL_MESSAGES: &[&str] = &[\n    \"That didn't seem to work!\",\n    \"那似乎没有用！\",\n    \"Oh my… I failed you 😞\",\n    \"哦，我…我让你失败😞\",\n    \"This didn't end well…\",\n    \"结局不好…\",\n];\nconst INFO_MESSAGES: &[&str] = &[\n    \"Making good progress!\",\n    \"进展良好！\",\n    \"Humming along…\",\n    \"嗡嗡作响…\",\n    \"It will be done soooooon…\",\n    \"会很快完成的……\",\n];\nconst SHORT_DELAY_MS: u64 = 50;\nconst WORK_DELAY_MS: u64 = 100;\nconst LONG_WORK_DELAY_MS: u64 = 2000;\nconst SPAWN_DELAY_MS: u64 = 200;\nconst CHANCE_TO_BLOCK_PER_STEP: f64 = 1.0 / 100.0;\nconst CHANCE_TO_SHOW_ETA: f64 = 0.5;\n\nmod shared;\nuse shared::{args, spawn};\n"
  },
  {
    "path": "examples/shared/args.rs",
    "content": "use argh::FromArgs;\n\n#[derive(FromArgs)]\n/// Reach new heights.\npub struct Options {\n    /// if set, the terminal window will be animated to assure resizing works as expected.\n    #[argh(switch, short = 'a')]\n    pub animate_terminal_size: bool,\n\n    /// if set, names of tasks will change rapidly, causing the delay at which column sizes are recalculated to show\n    #[argh(switch, short = 'c')]\n    pub changing_names: bool,\n\n    /// the amount of frames to show per second, can be below zero, e.g.\n    /// 0.25 shows a frame every 4 seconds.\n    #[argh(option, default = \"10.0\")]\n    pub fps: f32,\n\n    /// if set, recompute the column width of the task tree only every given frame. Otherwise the width will be recomputed every frame.\n    ///\n    /// Use this if there are many short-running tasks with varying names paired with high refresh rates of multiple frames per second to\n    /// stabilize the appearance of the TUI.\n    ///\n    /// 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\n    /// every 2 seconds.\n    #[argh(option, short = 'r')]\n    pub recompute_column_width_every_nth_frame: Option<usize>,\n\n    /// the amount of scrollback for task messages.\n    #[argh(option, default = \"80\")]\n    pub message_scrollback_buffer_size: usize,\n\n    /// the amount of pooled work chunks that can be created at most\n    #[argh(option, default = \"16\")]\n    pub pooled_work_max: usize,\n\n    /// the amount of pooled work chunks that should at least be created\n    #[argh(option, default = \"6\")]\n    pub pooled_work_min: usize,\n\n    /// multiplies the speed at which tasks seem to be running. Driving this down makes the TUI easier on the eyes\n    /// Defaults to 1.0. A valud of 0.5 halves the speed.\n    #[argh(option, short = 's', default = \"1.0\")]\n    pub speed_multitplier: f32,\n\n    /// for 'line' renderer: Determines the amount of seconds that the progress has to last at least until we see the first progress.\n    #[argh(option)]\n    pub line_initial_delay: Option<f32>,\n\n    /// for 'line' renderer: If true, timestamps will be displayed for each printed message.\n    #[argh(switch)]\n    pub line_timestamp: bool,\n\n    /// for 'line' renderer: The first level to display, defaults to 0\n    #[argh(option)]\n    pub line_start: Option<prodash::progress::key::Level>,\n\n    /// for 'line' renderer: Amount of columns we should draw into. If unset, the whole width of the terminal.\n    #[argh(option)]\n    pub line_column_count: Option<u16>,\n\n    /// for 'line' renderer: The first level to display, defaults to 1\n    #[argh(option)]\n    pub line_end: Option<prodash::progress::key::Level>,\n\n    /// set the renderer to use, defaults to \"tui\", and furthermore allows \"line\" and \"log\".\n    ///\n    /// If set ot \"log\", there will only be logging. Set 'RUST_LOG=info' before running the program to see them.\n    #[argh(option, short = 'R')]\n    pub renderer: Option<String>,\n}\n"
  },
  {
    "path": "examples/shared/mod.rs",
    "content": "use std::{ops::RangeInclusive, sync::Arc, time::Duration};\n\nuse futures_util::{future::FutureExt, stream::StreamExt};\nuse prodash::{\n    render::{\n        line,\n        tui::{self, ticker, Event, Interrupt, Line},\n    },\n    tree::Root as Tree,\n};\nuse rand::{prelude::IndexedRandom, rng, seq::SliceRandom, RngExt};\n\npub mod args;\nmod spawn;\npub use spawn::{spawn, Task};\n\nenum Direction {\n    Shrink,\n    Grow,\n}\n\nconst TITLES: &[&str] = &[\" Dashboard Demo \", \" 仪表板演示 \"];\n\npub fn launch_ambient_gui(\n    progress: Arc<Tree>,\n    renderer: &str,\n    args: args::Options,\n    throughput: bool,\n) -> std::result::Result<Task<()>, std::io::Error> {\n    let mut ticks: usize = 0;\n    let mut interruptible = true;\n    let render_fut = match renderer {\n        \"line\" => async move {\n            let mut handle = line::render(\n                std::io::stderr(),\n                Arc::downgrade(&progress),\n                line::Options {\n                    terminal_dimensions: args.line_column_count.map(|width| (width, 20)).unwrap_or((80, 20)),\n                    timestamp: args.line_timestamp,\n                    level_filter: Some(RangeInclusive::new(\n                        args.line_start.unwrap_or(1),\n                        args.line_end.unwrap_or(2),\n                    )),\n                    initial_delay: args.line_initial_delay.map(Duration::from_secs_f32),\n                    frames_per_second: args.fps,\n                    keep_running_if_progress_is_empty: true,\n                    throughput,\n                    ..Default::default()\n                }\n                .auto_configure(line::StreamKind::Stderr),\n            );\n            handle.disconnect();\n            blocking::unblock(move || handle.wait()).await;\n        }\n        .boxed(),\n        \"tui\" => {\n            if !is_terminal::is_terminal(std::io::stdout()) {\n                eprintln!(\"Need a terminal on stdout to draw progress TUI\");\n                futures_lite::future::ready(()).boxed()\n            } else {\n                tui::render_with_input(\n                    std::io::stdout(),\n                    Arc::downgrade(&progress),\n                    tui::Options {\n                        title: TITLES.choose(&mut rng()).copied().unwrap().into(),\n                        frames_per_second: args.fps,\n                        recompute_column_width_every_nth_frame: args.recompute_column_width_every_nth_frame,\n                        throughput,\n                        ..tui::Options::default()\n                    },\n                    futures_util::stream::select(\n                        window_resize_stream(args.animate_terminal_size),\n                        ticker(Duration::from_secs_f32((1.0 / args.fps).max(1.0))).map(move |_| {\n                            ticks += 1;\n                            if ticks % 2 == 0 {\n                                let is_interruptible = interruptible;\n                                interruptible = !interruptible;\n                                return if is_interruptible {\n                                    Event::SetInterruptMode(Interrupt::Instantly)\n                                } else {\n                                    Event::SetInterruptMode(Interrupt::Deferred)\n                                };\n                            }\n                            if rng().random_bool(0.5) {\n                                Event::SetTitle(TITLES.choose(&mut rng()).unwrap().to_string())\n                            } else {\n                                Event::SetInformation(generate_statistics())\n                            }\n                        }),\n                    ),\n                )?\n                .boxed()\n            }\n        }\n        _ => panic!(\"Unknown renderer: '{}'\", renderer),\n    };\n    let handle = spawn(render_fut.map(|_| ()));\n    Ok(handle)\n}\n\nfn generate_statistics() -> Vec<Line> {\n    let mut lines = vec![\n        Line::Text(\"You can put here what you want\".into()),\n        Line::Text(\"as long as it fits one line\".into()),\n        Line::Text(\"until a certain limit is reached\".into()),\n        Line::Text(\"which is when truncation happens\".into()),\n        Line::Text(\"这是中文的一些文字。\".into()),\n        Line::Text(\"鹅、鹅、鹅 曲项向天歌 白毛浮绿水 红掌拨清波\".into()),\n        Line::Text(\"床前明月光, 疑是地上霜。举头望明月，低头思故乡。\".into()),\n        Line::Text(\"锄禾日当午，汗滴禾下土。谁知盘中餐，粒粒皆辛苦。\".into()),\n        Line::Text(\"春眠不觉晓，处处闻啼鸟。夜来风雨声，花落知多少\".into()),\n        Line::Text(\"煮豆燃豆萁，豆在釜中泣。本自同根生，相煎何太急\".into()),\n        Line::Text(\"and this line is without any doubt very very long and it really doesn't want to stop\".into()),\n    ];\n    lines.shuffle(&mut rng());\n    lines.insert(0, Line::Title(\"Hello World\".into()));\n\n    lines.extend(vec![\n        Line::Title(\"Statistics\".into()),\n        Line::Text(format!(\n            \"lines of unsafe code: {}\",\n            rng().random_range(0usize..=1_000_000)\n        )),\n        Line::Text(format!(\n            \"wasted space in crates: {} Kb\",\n            rng().random_range(100usize..=1_000_000)\n        )),\n        Line::Text(format!(\n            \"unused dependencies: {} crates\",\n            rng().random_range(100usize..=1_000)\n        )),\n        Line::Text(format!(\n            \"average #dependencies: {} crates\",\n            rng().random_range(0usize..=500)\n        )),\n        Line::Text(format!(\"bloat in code: {} Kb\", rng().random_range(100usize..=5_000))),\n    ]);\n    lines\n}\n\nfn window_resize_stream(animate: bool) -> impl futures_core::Stream<Item = Event> {\n    let mut offset_xy = (0u16, 0u16);\n    let mut direction = Direction::Shrink;\n    if !animate {\n        return futures_lite::stream::pending().boxed();\n    }\n\n    ticker(Duration::from_millis(100))\n        .map(move |_| {\n            let (width, height) = crosstermion::terminal::size().unwrap_or((30, 30));\n            let (ref mut ofs_x, ref mut ofs_y) = offset_xy;\n            let min_size = 2;\n            match direction {\n                Direction::Shrink => {\n                    *ofs_x = ofs_x.saturating_add((1_f32 * (width as f32 / height as f32)).ceil() as u16);\n                    *ofs_y = ofs_y.saturating_add((1_f32 * (height as f32 / width as f32)).ceil() as u16);\n                }\n                Direction::Grow => {\n                    *ofs_x = ofs_x.saturating_sub((1_f32 * (width as f32 / height as f32)).ceil() as u16);\n                    *ofs_y = ofs_y.saturating_sub((1_f32 * (height as f32 / width as f32)).ceil() as u16);\n                }\n            }\n            let bound = tui::tui_export::layout::Rect {\n                x: 0,\n                y: 0,\n                width: width.saturating_sub(*ofs_x).max(min_size),\n                height: height.saturating_sub(*ofs_y).max(min_size),\n            };\n            if bound.area() <= u32::from(min_size) * u32::from(min_size)\n                || bound.area() == u32::from(width) * u32::from(height)\n            {\n                direction = match direction {\n                    Direction::Grow => Direction::Shrink,\n                    Direction::Shrink => Direction::Grow,\n                };\n            }\n            Event::SetWindowSize(bound)\n        })\n        .boxed()\n}\n"
  },
  {
    "path": "examples/shared/spawn.rs",
    "content": "// Copied and adapted from https://github.com/smol-rs/smol/blob/15447d6859df65fd1992f761ee46067bed62f8a5/src/spawn.rs\nuse std::{future::Future, panic::catch_unwind, thread};\n\nuse async_executor::Executor;\npub use async_executor::Task;\nuse futures_lite::future;\nuse once_cell::sync::Lazy;\n\npub fn spawn<T: Send + 'static>(future: impl Future<Output = T> + Send + 'static) -> Task<T> {\n    static GLOBAL: Lazy<Executor<'_>> = Lazy::new(|| {\n        thread::Builder::new()\n            .name(\"smol-one\".into())\n            .spawn(|| loop {\n                catch_unwind(|| async_io::block_on(GLOBAL.run(future::pending::<()>()))).ok();\n            })\n            .expect(\"cannot spawn executor thread\");\n\n        Executor::new()\n    });\n\n    GLOBAL.spawn(future)\n}\n"
  },
  {
    "path": "examples/units.rs",
    "content": "#![deny(unsafe_code)]\n\n#[cfg(not(feature = \"render-tui\"))]\ncompile_error!(\"The `render-tui` feature must be set, along with the `render-tui-crossterm`\");\n#[cfg(not(any(feature = \"render-tui-crossterm\")))]\ncompile_error!(\"Please set the 'render-tui-crossterm' feature when using the 'render-tui'\");\n\nuse std::{error::Error, sync::Arc};\n\nuse prodash::{tree::Root as Tree, unit};\nuse shared::args;\n\nfn main() -> Result {\n    env_logger::init();\n\n    let args: args::Options = argh::from_env();\n    let root = Tree::new();\n    let renderer = args.renderer.clone().unwrap_or_else(|| \"line\".into());\n    let handle = shared::launch_ambient_gui(root.clone(), &renderer, args, true).unwrap();\n    let work = async move {\n        let mut unblock = blocking::Unblock::new(());\n        unblock.with_mut(move |_| work_for_a_long_time_blocking(root)).await\n    };\n    futures_lite::pin!(work);\n    futures_lite::future::block_on(futures_util::future::select(handle, work));\n    Ok(())\n}\n\nfn work_for_a_long_time_blocking(root: Arc<Tree>) {\n    let bytes = root.add_child_with_id(\"download unknown\", *b\"DLUK\");\n    bytes.init(\n        None,\n        Some(unit::dynamic_and_mode(\n            unit::Bytes,\n            unit::display::Mode::with_throughput(),\n        )),\n    );\n    let bytes_max = root.add_child_with_id(\"download\", *b\"DLKN\");\n    bytes_max.init(\n        Some(100_000_000),\n        Some(unit::dynamic_and_mode(\n            unit::Bytes,\n            unit::display::Mode::with_percentage().and_throughput(),\n        )),\n    );\n\n    let duration = root.add_child_with_id(\"duration unknown\", *b\"DRUK\");\n    duration.init(None, Some(unit::dynamic(unit::Duration)));\n    let duration_max = root.add_child_with_id(\"duration\", *b\"DRKN\");\n    duration_max.init(\n        Some(60 * 60 * 24),\n        Some(unit::dynamic_and_mode(\n            unit::Duration,\n            unit::display::Mode::with_percentage().show_before_value(),\n        )),\n    );\n\n    fn formatter(decimals: usize) -> unit::human::Formatter {\n        let mut f = unit::human::Formatter::new();\n        f.with_decimals(decimals);\n        f\n    }\n    let human_count = root.add_child_with_id(\"item count unknown\", *b\"ITUK\");\n    human_count.init(\n        None,\n        Some(unit::dynamic_and_mode(\n            unit::Human::new(formatter(0), \"items\"),\n            unit::display::Mode::with_throughput(),\n        )),\n    );\n    let human_count_max = root.add_child_with_id(\"item count\", *b\"ITKN\");\n    human_count_max.init(\n        Some(7_542_241),\n        Some(unit::dynamic_and_mode(\n            unit::Human::new(formatter(2), \"items\"),\n            unit::display::Mode::with_percentage().and_throughput(),\n        )),\n    );\n\n    let steps = root.add_child_with_id(\"steps to take unknown\", *b\"STUK\");\n    steps.init(\n        None,\n        Some(unit::dynamic_and_mode(\n            unit::Range::new(\"steps\"),\n            unit::display::Mode::with_throughput(),\n        )),\n    );\n    let steps_max = root.add_child_with_id(\"steps to take\", *b\"STKN\");\n    steps_max.init(\n        Some(100),\n        Some(unit::dynamic_and_mode(\n            unit::Range::new(\"steps\"),\n            unit::display::Mode::with_percentage().and_throughput(),\n        )),\n    );\n\n    let steps_per_second = 10;\n    for step in 0.. {\n        bytes_max.inc_by(1_459_121);\n        bytes.inc_by(23_212_159);\n\n        duration.inc();\n        duration_max.inc_by(60);\n\n        human_count.inc_by(4);\n        human_count_max.inc_by(40274 / steps_per_second);\n\n        if step % steps_per_second == 0 {\n            steps.inc();\n            steps_max.inc();\n        }\n        std::thread::sleep(std::time::Duration::from_millis((1000 / steps_per_second) as u64));\n    }\n}\n\ntype Result = std::result::Result<(), Box<dyn Error + Send + 'static>>;\n\nmod shared;\n"
  },
  {
    "path": "rustfmt-nightly.toml",
    "content": "group_imports = \"StdExternalCrate\"\nimports_granularity = \"Crate\"\nmax_width = 120\n"
  },
  {
    "path": "rustfmt.toml",
    "content": "max_width = 120\n"
  },
  {
    "path": "src/lib.rs",
    "content": "#![deny(unsafe_code, missing_docs)]\n#![allow(clippy::empty_docs)]\n\n/*!\nProdash is a dashboard for displaying the progress of concurrent application.\n\nIt consists of two parts\n\n* a `Tree` to gather progress information and messages\n* a terminal user interface which displays this information, along with optional free-form information provided by the application itself\n\nEven though the `Tree` is not async, it's meant to be transparent and non-blocking performance wise, and benchmarks seem to indicate this\nis indeed the case.\n\nThe **terminal user interface** seems to be the least transparent part, but can be configured to refresh less frequently.\n\n# Terminal User Interface\n\nBy default, a TUI is provided to visualize all state. Have a look at [the example provided in the tui module](./render/tui/index.html).\n\n**Please note** that it is behind the `render-tui` feature toggle, which is enabled by default.\n\n# Logging\n\nIf the feature `progress-tree-log` is enabled, most calls to `progress` will also be logged.\nThat way, even without a terminal user interface, there will be progress messages.\nPlease note that logging to stdout should not be performed with this feature enabled and a terminal user interface is shown, as this will\nseriously interfere with the TUI.\n\n# A demo application\n\nPlease have a look at the [dashboard demo](https://github.com/GitoxideLabs/prodash/blob/main/examples/dashboard.rs).\n\n[![asciicast](https://asciinema.org/a/301838.svg)](https://asciinema.org/a/301838)\n\nRun it with `cargo run --example dashboard` and see what else it can do by checking out `cargo run --example dashboard -- --help`.\n */\n#[cfg(feature = \"progress-tree\")]\n///\npub mod tree;\n\n///\npub mod render;\n\n#[cfg(feature = \"progress-tree-log\")]\npub use log::info;\n#[cfg(feature = \"progress-tree-log\")]\npub use log::warn;\n\n#[cfg(any(feature = \"jiff\", feature = \"local-time\"))]\n///\npub mod time;\n\n///\npub mod unit;\n#[doc(inline)]\npub use unit::Unit;\n\n///\npub mod messages;\n///\npub mod progress;\n\nmod traits;\npub use traits::{\n    BoxedDynNestedProgress, BoxedProgress, Count, DynNestedProgress, DynNestedProgressToNestedProgress, NestedProgress,\n    Progress, Root, WeakRoot,\n};\n\nmod throughput;\npub use crate::throughput::Throughput;\n\n#[cfg(not(feature = \"progress-tree-log\"))]\nmod log {\n    /// Stub\n    #[macro_export(local_inner_macros)]\n    macro_rules! warn {\n        (target: $target:expr, $($arg:tt)+) => {};\n        ($($arg:tt)+) => {};\n    }\n    /// Stub\n    #[macro_export(local_inner_macros)]\n    macro_rules! info {\n        (target: $target:expr, $($arg:tt)+) => {};\n        ($($arg:tt)+) => {};\n    }\n}\n"
  },
  {
    "path": "src/messages.rs",
    "content": "use std::time::SystemTime;\n\n/// The severity of a message\n#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]\npub enum MessageLevel {\n    /// Rarely sent information related to the progress, not to be confused with the progress itself\n    Info,\n    /// Used to indicate that a task has failed, along with the reason\n    Failure,\n    /// Indicates a task was completed successfully\n    Success,\n}\n\n/// A message to be stored along with the progress tree.\n///\n/// It is created by [`Tree::message(…)`](./struct.Item.html#method.message).\n#[derive(Debug, Clone, Eq, PartialEq)]\npub struct Message {\n    /// The time at which the message was sent.\n    pub time: SystemTime,\n    /// The severity of the message\n    pub level: MessageLevel,\n    /// The name of the task that created the `Message`\n    pub origin: String,\n    /// The message itself\n    pub message: String,\n}\n\n/// A ring buffer for messages.\n#[derive(Debug, Clone, Eq, PartialEq)]\npub struct MessageRingBuffer {\n    pub(crate) buf: Vec<Message>,\n    cursor: usize,\n    total: usize,\n}\n\nimpl MessageRingBuffer {\n    /// Create a new instance the ability to hold `capacity` amount of messages.\n    pub fn with_capacity(capacity: usize) -> MessageRingBuffer {\n        MessageRingBuffer {\n            buf: Vec::with_capacity(capacity),\n            cursor: 0,\n            total: 0,\n        }\n    }\n\n    /// Push a `message` from `origin` at severity `level` into the buffer, possibly overwriting the last message added.\n    pub fn push_overwrite(&mut self, level: MessageLevel, origin: String, message: impl Into<String>) {\n        let msg = Message {\n            time: SystemTime::now(),\n            level,\n            origin,\n            message: message.into(),\n        };\n        if self.has_capacity() {\n            self.buf.push(msg)\n        } else {\n            self.buf[self.cursor] = msg;\n            self.cursor = (self.cursor + 1) % self.buf.len();\n        }\n        self.total = self.total.wrapping_add(1);\n    }\n\n    /// Copy all messages currently contained in the buffer to `out`.\n    pub fn copy_all(&self, out: &mut Vec<Message>) {\n        out.clear();\n        if self.buf.is_empty() {\n            return;\n        }\n        out.extend_from_slice(&self.buf[self.cursor % self.buf.len()..]);\n        if self.cursor != self.buf.len() {\n            out.extend_from_slice(&self.buf[..self.cursor]);\n        }\n    }\n\n    /// Copy all new messages into `out` that where received since the last time this method was called provided\n    /// its `previous` return value.\n    pub fn copy_new(&self, out: &mut Vec<Message>, previous: Option<MessageCopyState>) -> MessageCopyState {\n        out.clear();\n        match previous {\n            Some(MessageCopyState { cursor, buf_len, total }) => {\n                if self.total.saturating_sub(total) >= self.buf.capacity() {\n                    self.copy_all(out);\n                } else {\n                    let new_elements_below_cap = self.buf.len().saturating_sub(buf_len);\n                    let cursor_ofs: isize = self.cursor as isize - cursor as isize;\n                    match cursor_ofs {\n                        // there was some capacity left without wrapping around\n                        0 => {\n                            out.extend_from_slice(&self.buf[self.buf.len() - new_elements_below_cap..]);\n                        }\n                        // cursor advanced\n                        c if c > 0 => {\n                            out.extend_from_slice(&self.buf[(cursor % self.buf.len())..self.cursor]);\n                        }\n                        // cursor wrapped around\n                        c if c < 0 => {\n                            out.extend_from_slice(&self.buf[(cursor % self.buf.len())..]);\n                            out.extend_from_slice(&self.buf[..self.cursor]);\n                        }\n                        _ => unreachable!(\"logic dictates that… yeah, you really shouldn't ever see this!\"),\n                    }\n                }\n            }\n            None => self.copy_all(out),\n        };\n        MessageCopyState {\n            cursor: self.cursor,\n            buf_len: self.buf.len(),\n            total: self.total,\n        }\n    }\n\n    fn has_capacity(&self) -> bool {\n        self.buf.len() < self.buf.capacity()\n    }\n}\n\n/// State used to keep track of what's new since the last time message were copied.\n///\n/// Note that due to the nature of a ring buffer, there is no guarantee that you see all messages.\npub struct MessageCopyState {\n    cursor: usize,\n    buf_len: usize,\n    total: usize,\n}\n"
  },
  {
    "path": "src/progress/key.rs",
    "content": "use std::ops::{Index, IndexMut};\n\nuse crate::progress::Task;\n\n/// a level in the hierarchy of key components\n///\n/// _NOTE:_ This means we will show weird behaviour if there are more than 2^16 tasks at the same time on a level\n/// as multiple progress handles will manipulate the same state.\npub type Level = u8;\n\npub(crate) type Id = u16;\n\n/// A type identifying a spot in the hierarchy of `Tree` items.\n#[derive(Copy, Clone, Default, Hash, Eq, PartialEq, Ord, PartialOrd, Debug)]\npub struct Key(Option<Id>, Option<Id>, Option<Id>, Option<Id>, Option<Id>, Option<Id>);\n\n/// Determines if a sibling is above or below in the given level of hierarchy\n#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]\n#[allow(missing_docs)]\npub enum SiblingLocation {\n    Above,\n    Below,\n    AboveAndBelow,\n    #[default]\n    NotFound,\n}\n\nimpl SiblingLocation {\n    fn merge(&mut self, other: SiblingLocation) {\n        use SiblingLocation::*;\n        *self = match (*self, other) {\n            (any, NotFound) => any,\n            (NotFound, any) => any,\n            (Above, Below) => AboveAndBelow,\n            (Below, Above) => AboveAndBelow,\n            (AboveAndBelow, _) => AboveAndBelow,\n            (_, AboveAndBelow) => AboveAndBelow,\n            (Above, Above) => Above,\n            (Below, Below) => Below,\n        };\n    }\n}\n\n/// A type providing information about what's above and below `Tree` items.\n#[derive(Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd, Debug)]\npub struct Adjacency(\n    pub SiblingLocation,\n    pub SiblingLocation,\n    pub SiblingLocation,\n    pub SiblingLocation,\n    pub SiblingLocation,\n    pub SiblingLocation,\n);\n\nimpl Adjacency {\n    /// Return the level at which this sibling is located in the hierarchy.\n    pub fn level(&self) -> Level {\n        use SiblingLocation::*;\n        match self {\n            Adjacency(NotFound, NotFound, NotFound, NotFound, NotFound, NotFound) => 0,\n            Adjacency(_a, NotFound, NotFound, NotFound, NotFound, NotFound) => 1,\n            Adjacency(_a, _b, NotFound, NotFound, NotFound, NotFound) => 2,\n            Adjacency(_a, _b, _c, NotFound, NotFound, NotFound) => 3,\n            Adjacency(_a, _b, _c, _d, NotFound, NotFound) => 4,\n            Adjacency(_a, _b, _c, _d, _e, NotFound) => 5,\n            Adjacency(_a, _b, _c, _d, _e, _f) => 6,\n        }\n    }\n    /// Get a reference to the sibling location at `level`.\n    pub fn get(&self, level: Level) -> Option<&SiblingLocation> {\n        Some(match level {\n            1 => &self.0,\n            2 => &self.1,\n            3 => &self.2,\n            4 => &self.3,\n            5 => &self.4,\n            6 => &self.5,\n            _ => return None,\n        })\n    }\n    /// Get a mutable reference to the sibling location at `level`.\n    pub fn get_mut(&mut self, level: Level) -> Option<&mut SiblingLocation> {\n        Some(match level {\n            1 => &mut self.0,\n            2 => &mut self.1,\n            3 => &mut self.2,\n            4 => &mut self.3,\n            5 => &mut self.4,\n            6 => &mut self.5,\n            _ => return None,\n        })\n    }\n}\n\nimpl Index<Level> for Adjacency {\n    type Output = SiblingLocation;\n    fn index(&self, index: Level) -> &Self::Output {\n        self.get(index).expect(\"adjacency index in bound\")\n    }\n}\n\nimpl IndexMut<Level> for Adjacency {\n    fn index_mut(&mut self, index: Level) -> &mut Self::Output {\n        self.get_mut(index).expect(\"adjacency index in bound\")\n    }\n}\n\nimpl Key {\n    /// Return the key to the child identified by `child_id` located in a new nesting level below `self`.\n    pub fn add_child(self, child_id: Id) -> Key {\n        match self {\n            Key(None, None, None, None, None, None) => Key(Some(child_id), None, None, None, None, None),\n            Key(a, None, None, None, None, None) => Key(a, Some(child_id), None, None, None, None),\n            Key(a, b, None, None, None, None) => Key(a, b, Some(child_id), None, None, None),\n            Key(a, b, c, None, None, None) => Key(a, b, c, Some(child_id), None, None),\n            Key(a, b, c, d, None, None) => Key(a, b, c, d, Some(child_id), None),\n            Key(a, b, c, d, e, _f) => {\n                crate::warn!(\"Maximum nesting level reached. Adding tasks to current parent\");\n                Key(a, b, c, d, e, Some(child_id))\n            }\n        }\n    }\n\n    /// The level of hierarchy a node is placed in, i.e. the amount of path components\n    pub fn level(&self) -> Level {\n        match self {\n            Key(None, None, None, None, None, None) => 0,\n            Key(Some(_), None, None, None, None, None) => 1,\n            Key(Some(_), Some(_), None, None, None, None) => 2,\n            Key(Some(_), Some(_), Some(_), None, None, None) => 3,\n            Key(Some(_), Some(_), Some(_), Some(_), None, None) => 4,\n            Key(Some(_), Some(_), Some(_), Some(_), Some(_), None) => 5,\n            Key(Some(_), Some(_), Some(_), Some(_), Some(_), Some(_)) => 6,\n            _ => unreachable!(\"This is a bug - Keys follow a certain pattern\"),\n        }\n    }\n\n    /// Return the identifier for the item at `level`.\n    fn get(&self, level: Level) -> Option<&Id> {\n        match level {\n            1 => self.0.as_ref(),\n            2 => self.1.as_ref(),\n            3 => self.2.as_ref(),\n            4 => self.3.as_ref(),\n            5 => self.4.as_ref(),\n            6 => self.5.as_ref(),\n            _ => None,\n        }\n    }\n\n    /// Return true if the item identified by `other` shares the parent at `parent_level`.\n    pub fn shares_parent_with(&self, other: &Key, parent_level: Level) -> bool {\n        if parent_level < 1 {\n            return true;\n        }\n        for level in 1..=parent_level {\n            if let (Some(lhs), Some(rhs)) = (self.get(level), other.get(level)) {\n                if lhs != rhs {\n                    return false;\n                }\n            } else {\n                return false;\n            }\n        }\n        true\n    }\n\n    /// Compute the adjacency map for the key in `sorted` at the given `index`.\n    ///\n    /// It's vital that the invariant of `sorted` to actually be sorted by key is upheld\n    /// for the result to be reliable.\n    pub fn adjacency(sorted: &[(Key, Task)], index: usize) -> Adjacency {\n        use SiblingLocation::*;\n        let key = &sorted[index].0;\n        let key_level = key.level();\n        let mut adjecency = Adjacency::default();\n        if key_level == 0 {\n            return adjecency;\n        }\n\n        fn search<'a>(\n            iter: impl Iterator<Item = &'a (Key, Task)>,\n            key: &Key,\n            key_level: Level,\n            current_level: Level,\n            _id_at_level: Id,\n        ) -> Option<usize> {\n            iter.map(|(k, _)| k)\n                .take_while(|other| key.shares_parent_with(other, current_level.saturating_sub(1)))\n                .enumerate()\n                .find(|(_idx, k)| {\n                    if current_level == key_level {\n                        k.level() == key_level || k.level() + 1 == key_level\n                    } else {\n                        k.level() == current_level\n                    }\n                })\n                .map(|(idx, _)| idx)\n        }\n\n        let upward_iter = |from: usize, key: &Key, level: Level, id_at_level: Id| {\n            search(sorted[..from].iter().rev(), key, key_level, level, id_at_level)\n        };\n        let downward_iter = |from: usize, key: &Key, level: Level, id_at_level: Id| {\n            sorted\n                .get(from + 1..)\n                .and_then(|s| search(s.iter(), key, key_level, level, id_at_level))\n        };\n\n        {\n            let mut cursor = index;\n            for level in (1..=key_level).rev() {\n                if level == 1 {\n                    adjecency[level].merge(Above); // the root or any other sibling on level one\n                    continue;\n                }\n                if let Some(key_offset) = upward_iter(cursor, key, level, key[level]) {\n                    cursor = index.saturating_sub(key_offset);\n                    adjecency[level].merge(Above);\n                }\n            }\n        }\n        {\n            let mut cursor = index;\n            for level in (1..=key_level).rev() {\n                if let Some(key_offset) = downward_iter(cursor, key, level, key[level]) {\n                    cursor = index + key_offset;\n                    adjecency[level].merge(Below);\n                }\n            }\n        }\n        for level in 1..key_level {\n            if key_level == 1 && index + 1 == sorted.len() {\n                continue;\n            }\n            adjecency[level] = match adjecency[level] {\n                Above | Below | NotFound => NotFound,\n                AboveAndBelow => AboveAndBelow,\n            };\n        }\n        adjecency\n    }\n\n    /// The maximum amount of path components we can represent.\n    pub const fn max_level() -> Level {\n        6\n    }\n}\n\nimpl Index<Level> for Key {\n    type Output = Id;\n\n    fn index(&self, index: Level) -> &Self::Output {\n        self.get(index).expect(\"key index in bound\")\n    }\n}\n"
  },
  {
    "path": "src/progress/log.rs",
    "content": "use std::{\n    sync::{\n        atomic::{AtomicBool, Ordering},\n        Arc,\n    },\n    time::Duration,\n};\n\nuse crate::{\n    messages::MessageLevel,\n    progress::{Id, Step, StepShared},\n    Count, NestedProgress, Progress, Unit,\n};\n\n/// A [`NestedProgress`] implementation which displays progress as it happens without the use of a renderer.\n///\n/// Note that this incurs considerable performance cost as each progress calls ends up getting the system time\n/// to see if progress information should actually be emitted.\npub struct Log {\n    name: String,\n    id: Id,\n    max: Option<usize>,\n    unit: Option<Unit>,\n    step: StepShared,\n    current_level: usize,\n    max_level: usize,\n    trigger: Arc<AtomicBool>,\n}\n\nconst EMIT_LOG_EVERY_S: f32 = 0.5;\nconst SEP: &str = \"::\";\n\nimpl Log {\n    /// Create a new instance from `name` while displaying progress information only up to `max_level`.\n    pub fn new(name: impl Into<String>, max_level: Option<usize>) -> Self {\n        let trigger = Arc::new(AtomicBool::new(true));\n        std::thread::spawn({\n            let duration = Duration::from_secs_f32(EMIT_LOG_EVERY_S);\n            let trigger = Arc::downgrade(&trigger);\n            move || {\n                while let Some(t) = trigger.upgrade() {\n                    t.store(true, Ordering::Relaxed);\n                    std::thread::sleep(duration);\n                }\n            }\n        });\n        Log {\n            name: name.into(),\n            id: crate::progress::UNKNOWN,\n            current_level: 0,\n            max_level: max_level.unwrap_or(usize::MAX),\n            max: None,\n            step: Default::default(),\n            unit: None,\n            trigger,\n        }\n    }\n}\n\nimpl Log {\n    fn maybe_log(&self) {\n        if self.current_level > self.max_level {\n            return;\n        }\n        let step = self.step();\n        if self.trigger.swap(false, Ordering::Relaxed) {\n            match (self.max, &self.unit) {\n                (max, Some(unit)) => log::info!(\"{} → {}\", self.name, unit.display(step, max, None)),\n                (Some(max), None) => log::info!(\"{} → {} / {}\", self.name, step, max),\n                (None, None) => log::info!(\"{} → {}\", self.name, step),\n            }\n        }\n    }\n}\n\nimpl Count for Log {\n    fn set(&self, step: Step) {\n        self.step.store(step, Ordering::SeqCst);\n        self.maybe_log()\n    }\n\n    fn step(&self) -> usize {\n        self.step.load(Ordering::Relaxed)\n    }\n\n    fn inc_by(&self, step: Step) {\n        self.step.fetch_add(step, Ordering::Relaxed);\n        self.maybe_log()\n    }\n\n    fn counter(&self) -> StepShared {\n        self.step.clone()\n    }\n}\n\nimpl Progress for Log {\n    fn init(&mut self, max: Option<Step>, unit: Option<Unit>) {\n        self.max = max;\n        self.unit = unit;\n    }\n    fn unit(&self) -> Option<Unit> {\n        self.unit.clone()\n    }\n\n    fn max(&self) -> Option<Step> {\n        self.max\n    }\n\n    fn set_max(&mut self, max: Option<Step>) -> Option<Step> {\n        let prev = self.max;\n        self.max = max;\n        prev\n    }\n\n    fn set_name(&mut self, name: String) {\n        self.name = self\n            .name\n            .split(\"::\")\n            .next()\n            .map(|parent| format!(\"{}{}{}\", parent.to_owned(), SEP, name))\n            .unwrap_or(name);\n    }\n\n    fn name(&self) -> Option<String> {\n        self.name.split(SEP).nth(1).map(ToOwned::to_owned)\n    }\n\n    fn id(&self) -> Id {\n        self.id\n    }\n\n    fn message(&self, level: MessageLevel, message: String) {\n        match level {\n            MessageLevel::Info => log::info!(\"ℹ{} → {}\", self.name, message),\n            MessageLevel::Failure => log::error!(\"𐄂{} → {}\", self.name, message),\n            MessageLevel::Success => log::info!(\"✓{} → {}\", self.name, message),\n        }\n    }\n}\n\nimpl NestedProgress for Log {\n    type SubProgress = Log;\n\n    fn add_child(&mut self, name: impl Into<String>) -> Self::SubProgress {\n        self.add_child_with_id(name, crate::progress::UNKNOWN)\n    }\n\n    fn add_child_with_id(&mut self, name: impl Into<String>, id: Id) -> Self::SubProgress {\n        Log {\n            name: format!(\"{}{}{}\", self.name, SEP, Into::<String>::into(name)),\n            id,\n            current_level: self.current_level + 1,\n            max_level: self.max_level,\n            step: Default::default(),\n            max: None,\n            unit: None,\n            trigger: Arc::clone(&self.trigger),\n        }\n    }\n}\n"
  },
  {
    "path": "src/progress/mod.rs",
    "content": "use std::{\n    sync::{\n        atomic::{AtomicUsize, Ordering},\n        Arc,\n    },\n    time::SystemTime,\n};\n\nuse crate::unit::Unit;\n\n///\npub mod key;\n#[doc(inline)]\npub use key::Key;\n\nmod utils;\n\n#[cfg(feature = \"progress-log\")]\nmod log;\npub use utils::{Discard, DoOrDiscard, Either, ThroughputOnDrop};\n\n#[cfg(feature = \"progress-log\")]\npub use self::log::Log;\n\n/// Four bytes of function-local unique and stable identifier for each item added as progress,\n/// like b\"TREE\" or b\"FILE\".\n///\n/// Note that uniqueness only relates to one particular method call where those interested in its progress\n/// may assume certain stable ids to look for when selecting specific bits of progress to process.\npub type Id = [u8; 4];\n\n/// The default Id to use if there is no need for an id.\n///\n/// This is the default unless applications wish to make themselves more introspectable.\npub const UNKNOWN: Id = *b\"\\0\\0\\0\\0\";\n\n/// The amount of steps a progress can make\npub type Step = usize;\n\n/// The amount of steps a progress can make, for threadsafe counting.\npub type AtomicStep = AtomicUsize;\n\n/// As step, but shareable.\npub type StepShared = Arc<AtomicStep>;\n\n/// Indicate whether a progress can or cannot be made.\n#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)]\npub enum State {\n    /// Indicates a task is blocked and cannot indicate progress, optionally until the\n    /// given time. The task cannot easily be interrupted.\n    Blocked(&'static str, Option<SystemTime>),\n    /// Indicates a task cannot indicate progress, optionally until the\n    /// given time. The task can be interrupted.\n    Halted(&'static str, Option<SystemTime>),\n    /// The task is running\n    #[default]\n    Running,\n}\n\n/// Progress associated with some item in the progress tree.\n#[derive(Clone, Default, Debug)]\npub struct Value {\n    /// The amount of progress currently made\n    pub step: StepShared,\n    /// The step at which no further progress has to be made.\n    ///\n    /// If unset, the progress is unbounded.\n    pub done_at: Option<Step>,\n    /// The unit associated with the progress.\n    pub unit: Option<Unit>,\n    /// Whether progress can be made or not\n    pub state: State,\n}\n\nimpl std::hash::Hash for Value {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        let Self {\n            step,\n            done_at,\n            unit,\n            state: our_state,\n        } = self;\n        done_at.hash(state);\n        unit.hash(state);\n        our_state.hash(state);\n        step.load(Ordering::Relaxed).hash(state);\n    }\n}\n\nimpl Value {\n    /// Returns a number between `Some(0.0)` and `Some(1.0)`, or `None` if the progress is unbounded.\n    ///\n    /// A task half done would return `Some(0.5)`.\n    pub fn fraction(&self) -> Option<f32> {\n        self.done_at\n            .map(|done_at| self.step.load(Ordering::SeqCst) as f32 / done_at as f32)\n    }\n}\n\n/// The value associated with a spot in the hierarchy.\n#[derive(Clone, Default, Debug, Hash)]\npub struct Task {\n    /// The name of the `Item` or task.\n    pub name: String,\n    /// The stable identifier of this task.\n    /// Useful for selecting specific tasks out of a set of them.\n    pub id: Id,\n    /// The progress itself, unless this value belongs to an `Item` serving as organizational unit.\n    pub progress: Option<Value>,\n}\n"
  },
  {
    "path": "src/progress/utils.rs",
    "content": "use crate::{messages::MessageLevel, progress::Id, Count, NestedProgress, Progress, Unit};\nuse std::sync::atomic::AtomicUsize;\nuse std::sync::Arc;\n\n/// An implementation of [`NestedProgress`] which discards all calls.\npub struct Discard;\n\nimpl Count for Discard {\n    fn set(&self, _step: usize) {}\n\n    fn step(&self) -> usize {\n        0\n    }\n\n    fn inc_by(&self, _step: usize) {}\n\n    fn counter(&self) -> StepShared {\n        Arc::new(AtomicUsize::default())\n    }\n}\n\nimpl Progress for Discard {\n    fn init(&mut self, _max: Option<usize>, _unit: Option<Unit>) {}\n\n    fn set_max(&mut self, _max: Option<Step>) -> Option<Step> {\n        None\n    }\n    fn set_name(&mut self, _name: String) {}\n\n    fn name(&self) -> Option<String> {\n        None\n    }\n\n    fn id(&self) -> Id {\n        crate::progress::UNKNOWN\n    }\n\n    fn message(&self, _level: MessageLevel, _message: String) {}\n}\n\nimpl NestedProgress for Discard {\n    type SubProgress = Self;\n\n    fn add_child(&mut self, _name: impl Into<String>) -> Self {\n        Discard\n    }\n\n    fn add_child_with_id(&mut self, _name: impl Into<String>, _id: Id) -> Self {\n        Discard\n    }\n}\n\n/// An implementation of [`NestedProgress`] showing either one or the other implementation.\n///\n/// Useful in conjunction with [`Discard`] and a working implementation, making it as a form of `Option<Progress>` which\n/// can be passed to methods requiring `impl Progress`.\n/// See [`DoOrDiscard`] for an incarnation of this.\n#[allow(missing_docs)]\npub enum Either<L, R> {\n    Left(L),\n    Right(R),\n}\n\nimpl<L, R> Count for Either<L, R>\nwhere\n    L: Count,\n    R: Count,\n{\n    fn set(&self, step: usize) {\n        match self {\n            Either::Left(l) => l.set(step),\n            Either::Right(r) => r.set(step),\n        }\n    }\n    fn step(&self) -> usize {\n        match self {\n            Either::Left(l) => l.step(),\n            Either::Right(r) => r.step(),\n        }\n    }\n    fn inc_by(&self, step: usize) {\n        match self {\n            Either::Left(l) => l.inc_by(step),\n            Either::Right(r) => r.inc_by(step),\n        }\n    }\n    fn counter(&self) -> StepShared {\n        match self {\n            Either::Left(l) => l.counter(),\n            Either::Right(r) => r.counter(),\n        }\n    }\n}\n\nimpl<L, R> Progress for Either<L, R>\nwhere\n    L: Progress,\n    R: Progress,\n{\n    fn init(&mut self, max: Option<usize>, unit: Option<Unit>) {\n        match self {\n            Either::Left(l) => l.init(max, unit),\n            Either::Right(r) => r.init(max, unit),\n        }\n    }\n\n    fn unit(&self) -> Option<Unit> {\n        match self {\n            Either::Left(l) => l.unit(),\n            Either::Right(r) => r.unit(),\n        }\n    }\n\n    fn max(&self) -> Option<usize> {\n        match self {\n            Either::Left(l) => l.max(),\n            Either::Right(r) => r.max(),\n        }\n    }\n\n    fn set_max(&mut self, max: Option<Step>) -> Option<Step> {\n        match self {\n            Either::Left(l) => l.set_max(max),\n            Either::Right(r) => r.set_max(max),\n        }\n    }\n\n    fn set_name(&mut self, name: String) {\n        match self {\n            Either::Left(l) => l.set_name(name),\n            Either::Right(r) => r.set_name(name),\n        }\n    }\n\n    fn name(&self) -> Option<String> {\n        match self {\n            Either::Left(l) => l.name(),\n            Either::Right(r) => r.name(),\n        }\n    }\n\n    fn id(&self) -> Id {\n        match self {\n            Either::Left(l) => l.id(),\n            Either::Right(r) => r.id(),\n        }\n    }\n\n    fn message(&self, level: MessageLevel, message: String) {\n        match self {\n            Either::Left(l) => l.message(level, message),\n            Either::Right(r) => r.message(level, message),\n        }\n    }\n}\n\nimpl<L, R> NestedProgress for Either<L, R>\nwhere\n    L: NestedProgress,\n    R: NestedProgress,\n{\n    type SubProgress = Either<L::SubProgress, R::SubProgress>;\n\n    fn add_child(&mut self, name: impl Into<String>) -> Self::SubProgress {\n        match self {\n            Either::Left(l) => Either::Left(l.add_child(name)),\n            Either::Right(r) => Either::Right(r.add_child(name)),\n        }\n    }\n\n    fn add_child_with_id(&mut self, name: impl Into<String>, id: Id) -> Self::SubProgress {\n        match self {\n            Either::Left(l) => Either::Left(l.add_child_with_id(name, id)),\n            Either::Right(r) => Either::Right(r.add_child_with_id(name, id)),\n        }\n    }\n}\n\n/// An implementation of `Progress` which can be created easily from `Option<impl Progress>`.\npub struct DoOrDiscard<T>(Either<T, Discard>);\n\nimpl<T> From<Option<T>> for DoOrDiscard<T>\nwhere\n    T: NestedProgress,\n{\n    fn from(p: Option<T>) -> Self {\n        match p {\n            Some(p) => DoOrDiscard(Either::Left(p)),\n            None => DoOrDiscard(Either::Right(Discard)),\n        }\n    }\n}\n\nimpl<T: NestedProgress> DoOrDiscard<T> {\n    /// Obtain either the original [`NestedProgress`] implementation or `None`.\n    pub fn into_inner(self) -> Option<T> {\n        match self {\n            DoOrDiscard(Either::Left(p)) => Some(p),\n            DoOrDiscard(Either::Right(_)) => None,\n        }\n    }\n\n    /// Take out the implementation of [`NestedProgress`] and replace it with [`Discard`].\n    pub fn take(&mut self) -> Option<T> {\n        let this = std::mem::replace(self, DoOrDiscard::from(None));\n        match this {\n            DoOrDiscard(Either::Left(p)) => Some(p),\n            DoOrDiscard(Either::Right(_)) => None,\n        }\n    }\n}\n\nimpl<T> Count for DoOrDiscard<T>\nwhere\n    T: Count,\n{\n    fn set(&self, step: usize) {\n        self.0.set(step)\n    }\n    fn step(&self) -> usize {\n        self.0.step()\n    }\n\n    fn inc_by(&self, step: usize) {\n        self.0.inc_by(step)\n    }\n\n    fn counter(&self) -> StepShared {\n        self.0.counter()\n    }\n}\n\nimpl<T> Progress for DoOrDiscard<T>\nwhere\n    T: Progress,\n{\n    fn init(&mut self, max: Option<usize>, unit: Option<Unit>) {\n        self.0.init(max, unit)\n    }\n\n    fn unit(&self) -> Option<Unit> {\n        self.0.unit()\n    }\n\n    fn max(&self) -> Option<usize> {\n        self.0.max()\n    }\n\n    fn set_max(&mut self, max: Option<Step>) -> Option<Step> {\n        self.0.set_max(max)\n    }\n\n    fn set_name(&mut self, name: String) {\n        self.0.set_name(name);\n    }\n\n    fn name(&self) -> Option<String> {\n        self.0.name()\n    }\n\n    fn id(&self) -> Id {\n        self.0.id()\n    }\n\n    fn message(&self, level: MessageLevel, message: String) {\n        self.0.message(level, message)\n    }\n}\n\nimpl<T> NestedProgress for DoOrDiscard<T>\nwhere\n    T: NestedProgress,\n{\n    type SubProgress = DoOrDiscard<T::SubProgress>;\n\n    fn add_child(&mut self, name: impl Into<String>) -> Self::SubProgress {\n        DoOrDiscard(self.0.add_child(name))\n    }\n\n    fn add_child_with_id(&mut self, name: impl Into<String>, id: Id) -> Self::SubProgress {\n        DoOrDiscard(self.0.add_child_with_id(name, id))\n    }\n}\n\nuse std::time::Instant;\n\nuse crate::progress::{Step, StepShared};\n\n/// Emit a message with throughput information when the instance is dropped.\npub struct ThroughputOnDrop<T: NestedProgress>(T, Instant);\n\nimpl<T: NestedProgress> ThroughputOnDrop<T> {\n    /// Create a new instance by providing the `inner` [`NestedProgress`] implementation.\n    pub fn new(inner: T) -> Self {\n        ThroughputOnDrop(inner, Instant::now())\n    }\n}\n\nimpl<T: NestedProgress> Count for ThroughputOnDrop<T> {\n    fn set(&self, step: usize) {\n        self.0.set(step)\n    }\n\n    fn step(&self) -> usize {\n        self.0.step()\n    }\n\n    fn inc_by(&self, step: usize) {\n        self.0.inc_by(step)\n    }\n\n    fn counter(&self) -> StepShared {\n        self.0.counter()\n    }\n}\n\nimpl<T: NestedProgress> Progress for ThroughputOnDrop<T> {\n    fn init(&mut self, max: Option<usize>, unit: Option<Unit>) {\n        self.0.init(max, unit)\n    }\n\n    fn unit(&self) -> Option<Unit> {\n        self.0.unit()\n    }\n\n    fn max(&self) -> Option<usize> {\n        self.0.max()\n    }\n\n    fn set_max(&mut self, max: Option<Step>) -> Option<Step> {\n        self.0.set_max(max)\n    }\n\n    fn set_name(&mut self, name: String) {\n        self.0.set_name(name)\n    }\n\n    fn name(&self) -> Option<String> {\n        self.0.name()\n    }\n\n    fn id(&self) -> Id {\n        self.0.id()\n    }\n\n    fn message(&self, level: MessageLevel, message: String) {\n        self.0.message(level, message)\n    }\n}\n\nimpl<T: NestedProgress> NestedProgress for ThroughputOnDrop<T> {\n    type SubProgress = ThroughputOnDrop<T::SubProgress>;\n\n    fn add_child(&mut self, name: impl Into<String>) -> Self::SubProgress {\n        ThroughputOnDrop::new(self.0.add_child(name))\n    }\n\n    fn add_child_with_id(&mut self, name: impl Into<String>, id: Id) -> Self::SubProgress {\n        ThroughputOnDrop::new(self.0.add_child_with_id(name, id))\n    }\n}\n\nimpl<T: NestedProgress> Drop for ThroughputOnDrop<T> {\n    fn drop(&mut self) {\n        self.0.show_throughput(self.1)\n    }\n}\n"
  },
  {
    "path": "src/render/line/draw.rs",
    "content": "use std::{\n    collections::{hash_map::DefaultHasher, VecDeque},\n    hash::{Hash, Hasher},\n    io,\n    ops::RangeInclusive,\n    sync::atomic::Ordering,\n};\n\nuse crosstermion::{\n    color,\n    nu_ansi_term::{AnsiString, AnsiStrings, Color, Style},\n};\nuse unicode_width::UnicodeWidthStr;\n\nuse crate::{\n    messages::{Message, MessageCopyState, MessageLevel},\n    progress::{self, Value},\n    unit, Root, Throughput,\n};\n\n#[derive(Default)]\npub struct State {\n    tree: Vec<(progress::Key, progress::Task)>,\n    tree_hash: u64,\n    messages: Vec<Message>,\n    for_next_copy: Option<MessageCopyState>,\n    /// The size of the message origin, tracking the terminal height so things potentially off screen don't influence width anymore.\n    message_origin_size: VecDeque<usize>,\n    /// The maximum progress midpoint (point till progress bar starts) seen at the last tick\n    last_progress_midpoint: Option<u16>,\n    /// The amount of blocks per line we have written last time.\n    blocks_per_line: VecDeque<u16>,\n    pub throughput: Option<Throughput>,\n}\n\nimpl State {\n    pub(crate) fn update_from_progress(&mut self, progress: &impl Root) -> bool {\n        progress.sorted_snapshot(&mut self.tree);\n        let mut hasher = DefaultHasher::new();\n        self.tree.hash(&mut hasher);\n        let cur_hash = hasher.finish();\n\n        self.for_next_copy = progress\n            .copy_new_messages(&mut self.messages, self.for_next_copy.take())\n            .into();\n        let changed = self.tree_hash != cur_hash;\n        self.tree_hash = cur_hash;\n        changed\n    }\n    pub(crate) fn clear(&mut self) {\n        self.tree.clear();\n        self.messages.clear();\n        self.for_next_copy.take();\n    }\n}\n\npub struct Options {\n    pub level_filter: Option<RangeInclusive<progress::key::Level>>,\n    pub terminal_dimensions: (u16, u16),\n    pub keep_running_if_progress_is_empty: bool,\n    pub output_is_terminal: bool,\n    pub colored: bool,\n    pub timestamp: bool,\n    pub hide_cursor: bool,\n}\n\nfn messages(\n    out: &mut impl io::Write,\n    state: &mut State,\n    colored: bool,\n    max_height: usize,\n    timestamp: bool,\n) -> io::Result<()> {\n    let mut brush = color::Brush::new(colored);\n    fn to_color(level: MessageLevel) -> Color {\n        use crate::messages::MessageLevel::*;\n        match level {\n            Info => Color::White,\n            Success => Color::Green,\n            Failure => Color::Red,\n        }\n    }\n    let mut tokens: Vec<AnsiString<'_>> = Vec::with_capacity(6);\n    let mut current_maximum = state.message_origin_size.iter().max().cloned().unwrap_or(0);\n    for Message {\n        time,\n        level,\n        origin,\n        message,\n    } in &state.messages\n    {\n        tokens.clear();\n        let blocks_drawn_during_previous_tick = state.blocks_per_line.pop_front().unwrap_or(0);\n        let message_block_len = origin.width();\n        current_maximum = current_maximum.max(message_block_len);\n        if state.message_origin_size.len() == max_height {\n            state.message_origin_size.pop_front();\n        }\n        state.message_origin_size.push_back(message_block_len);\n\n        let color = to_color(*level);\n        tokens.push(\" \".into());\n        if timestamp {\n            tokens.push(\n                brush\n                    .style(color.dimmed().on(Color::Yellow))\n                    .paint(crate::time::format_time_for_messages(*time)),\n            );\n            tokens.push(Style::default().paint(\" \"));\n        } else {\n            tokens.push(\"\".into());\n        };\n        tokens.push(brush.style(Style::default().dimmed()).paint(format!(\n            \"{:>fill_size$}{}\",\n            \"\",\n            origin,\n            fill_size = current_maximum - message_block_len,\n        )));\n        tokens.push(\" \".into());\n        tokens.push(brush.style(color.bold()).paint(message));\n        let message_block_count = block_count_sans_ansi_codes(&tokens);\n        write!(out, \"{}\", AnsiStrings(tokens.as_slice()))?;\n\n        if blocks_drawn_during_previous_tick > message_block_count {\n            newline_with_overdraw(out, &tokens, blocks_drawn_during_previous_tick)?;\n        } else {\n            writeln!(out)?;\n        }\n    }\n    Ok(())\n}\n\npub fn all(out: &mut impl io::Write, show_progress: bool, state: &mut State, config: &Options) -> io::Result<()> {\n    if !config.keep_running_if_progress_is_empty && state.tree.is_empty() {\n        return Err(io::Error::other(\"stop as progress is empty\"));\n    }\n    messages(\n        out,\n        state,\n        config.colored,\n        config.terminal_dimensions.1 as usize,\n        config.timestamp,\n    )?;\n\n    if show_progress && config.output_is_terminal {\n        if let Some(tp) = state.throughput.as_mut() {\n            tp.update_elapsed();\n        }\n        let level_range = config\n            .level_filter\n            .clone()\n            .unwrap_or(RangeInclusive::new(0, progress::key::Level::MAX));\n        let lines_to_be_drawn = state\n            .tree\n            .iter()\n            .filter(|(k, _)| level_range.contains(&k.level()))\n            .count();\n        if state.blocks_per_line.len() < lines_to_be_drawn {\n            state.blocks_per_line.resize(lines_to_be_drawn, 0);\n        }\n        let mut tokens: Vec<AnsiString<'_>> = Vec::with_capacity(4);\n        let mut max_midpoint = 0;\n        for ((key, value), ref mut blocks_in_last_iteration) in state\n            .tree\n            .iter()\n            .filter(|(k, _)| level_range.contains(&k.level()))\n            .zip(state.blocks_per_line.iter_mut())\n        {\n            max_midpoint = max_midpoint.max(\n                format_progress(\n                    key,\n                    value,\n                    config.terminal_dimensions.0,\n                    config.colored,\n                    state.last_progress_midpoint,\n                    state\n                        .throughput\n                        .as_mut()\n                        .and_then(|tp| tp.update_and_get(key, value.progress.as_ref())),\n                    &mut tokens,\n                )\n                .unwrap_or(0),\n            );\n            write!(out, \"{}\", AnsiStrings(tokens.as_slice()))?;\n\n            **blocks_in_last_iteration = newline_with_overdraw(out, &tokens, **blocks_in_last_iteration)?;\n        }\n        if let Some(tp) = state.throughput.as_mut() {\n            tp.reconcile(&state.tree);\n        }\n        state.last_progress_midpoint = Some(max_midpoint);\n        // overwrite remaining lines that we didn't touch naturally\n        let lines_drawn = lines_to_be_drawn;\n        if state.blocks_per_line.len() > lines_drawn {\n            for blocks_in_last_iteration in state.blocks_per_line.iter().skip(lines_drawn) {\n                writeln!(out, \"{:>width$}\", \"\", width = *blocks_in_last_iteration as usize)?;\n            }\n            // Move cursor back to end of the portion we have actually drawn\n            crosstermion::execute!(out, crosstermion::cursor::MoveUp(state.blocks_per_line.len() as u16))?;\n            state.blocks_per_line.resize(lines_drawn, 0);\n        } else if lines_drawn > 0 {\n            crosstermion::execute!(out, crosstermion::cursor::MoveUp(lines_drawn as u16))?;\n        }\n    }\n    Ok(())\n}\n\n/// Must be called directly after `tokens` were drawn, without newline. Takes care of adding the newline.\nfn newline_with_overdraw(\n    out: &mut impl io::Write,\n    tokens: &[AnsiString<'_>],\n    blocks_in_last_iteration: u16,\n) -> io::Result<u16> {\n    let current_block_count = block_count_sans_ansi_codes(tokens);\n    if blocks_in_last_iteration > current_block_count {\n        // fill to the end of line to overwrite what was previously there\n        writeln!(\n            out,\n            \"{:>width$}\",\n            \"\",\n            width = (blocks_in_last_iteration - current_block_count) as usize\n        )?;\n    } else {\n        writeln!(out)?;\n    };\n    Ok(current_block_count)\n}\n\nfn block_count_sans_ansi_codes(strings: &[AnsiString<'_>]) -> u16 {\n    strings.iter().map(|s| s.as_str().width() as u16).sum()\n}\n\nfn draw_progress_bar(p: &Value, style: Style, mut blocks_available: u16, colored: bool, buf: &mut Vec<AnsiString<'_>>) {\n    let mut brush = color::Brush::new(colored);\n    let styled_brush = brush.style(style);\n\n    blocks_available = blocks_available.saturating_sub(3); // account for…I don't really know it's magic\n    buf.push(\" [\".into());\n    match p.fraction() {\n        Some(mut fraction) => {\n            fraction = fraction.min(1.0);\n            blocks_available = blocks_available.saturating_sub(1); // account for '>' apparently\n            let progress_blocks = (blocks_available as f32 * fraction).floor() as usize;\n            buf.push(styled_brush.paint(format!(\"{:=<width$}\", \"\", width = progress_blocks)));\n            buf.push(styled_brush.paint(\">\"));\n            buf.push(styled_brush.style(style.dimmed()).paint(format!(\n                \"{:-<width$}\",\n                \"\",\n                width = (blocks_available - progress_blocks as u16) as usize\n            )));\n        }\n        None => {\n            const CHARS: [char; 6] = ['=', '=', '=', ' ', ' ', ' '];\n            buf.push(\n                styled_brush.paint(\n                    (p.step.load(Ordering::SeqCst)..usize::MAX)\n                        .take(blocks_available as usize)\n                        .map(|idx| CHARS[idx % CHARS.len()])\n                        .rev()\n                        .collect::<String>(),\n                ),\n            );\n        }\n    }\n    buf.push(\"]\".into());\n}\n\nfn progress_style(p: &Value) -> Style {\n    use crate::progress::State::*;\n    match p.state {\n        Running => if let Some(fraction) = p.fraction() {\n            if fraction > 0.8 {\n                Color::Green\n            } else {\n                Color::Yellow\n            }\n        } else {\n            Color::White\n        }\n        .normal(),\n        Halted(_, _) => Color::Red.dimmed(),\n        Blocked(_, _) => Color::Red.normal(),\n    }\n}\n\nfn format_progress<'a>(\n    key: &progress::Key,\n    value: &'a progress::Task,\n    column_count: u16,\n    colored: bool,\n    midpoint: Option<u16>,\n    throughput: Option<unit::display::Throughput>,\n    buf: &mut Vec<AnsiString<'a>>,\n) -> Option<u16> {\n    let mut brush = color::Brush::new(colored);\n    buf.clear();\n\n    buf.push(Style::new().paint(format!(\"{:>level$}\", \"\", level = key.level() as usize)));\n    match value.progress.as_ref() {\n        Some(progress) => {\n            let style = progress_style(progress);\n            buf.push(brush.style(Color::Cyan.bold()).paint(&value.name));\n            buf.push(\" \".into());\n\n            let pre_unit = buf.len();\n            let values_brush = brush.style(Style::new().bold().dimmed());\n            match progress.unit.as_ref() {\n                Some(unit) => {\n                    let mut display = unit.display(progress.step.load(Ordering::SeqCst), progress.done_at, throughput);\n                    buf.push(values_brush.paint(display.values().to_string()));\n                    buf.push(\" \".into());\n                    buf.push(display.unit().to_string().into());\n                }\n                None => {\n                    buf.push(values_brush.paint(match progress.done_at {\n                        Some(done_at) => format!(\"{}/{}\", progress.step.load(Ordering::SeqCst), done_at),\n                        None => format!(\"{}\", progress.step.load(Ordering::SeqCst)),\n                    }));\n                }\n            }\n            let desired_midpoint = block_count_sans_ansi_codes(buf.as_slice());\n            let actual_midpoint = if let Some(midpoint) = midpoint {\n                let padding = midpoint.saturating_sub(desired_midpoint);\n                if padding > 0 {\n                    buf.insert(pre_unit, \" \".repeat(padding as usize).into());\n                }\n                block_count_sans_ansi_codes(buf.as_slice())\n            } else {\n                desired_midpoint\n            };\n            let blocks_left = column_count.saturating_sub(actual_midpoint);\n            if blocks_left > 0 {\n                draw_progress_bar(progress, style, blocks_left, colored, buf);\n            }\n            Some(desired_midpoint)\n        }\n        None => {\n            // headline only - FIXME: would have to truncate it if it is too long for the line…\n            buf.push(brush.style(Color::White.bold()).paint(&value.name));\n            None\n        }\n    }\n}\n"
  },
  {
    "path": "src/render/line/engine.rs",
    "content": "#[cfg(feature = \"signal-hook\")]\nuse std::sync::Arc;\nuse std::{\n    io,\n    ops::RangeInclusive,\n    sync::atomic::{AtomicBool, Ordering},\n    time::Duration,\n};\n\nuse crate::{progress, render::line::draw, Throughput, WeakRoot};\n\n/// Options used for configuring a [line renderer][render()].\n#[derive(Clone)]\npub struct Options {\n    /// If true, _(default true)_, we assume the output stream belongs to a terminal.\n    ///\n    /// If false, we won't print any live progress, only log messages.\n    pub output_is_terminal: bool,\n\n    /// If true, _(default: true)_ we will display color. You should use `output_is_terminal && crosstermion::should_colorize()`\n    /// to determine this value.\n    ///\n    /// Please note that you can enforce color even if the output stream is not connected to a terminal by setting\n    /// this field to true.\n    pub colored: bool,\n\n    /// If true, _(default: false)_, a timestamp will be shown before each message.\n    pub timestamp: bool,\n\n    /// The amount of columns and rows to use for drawing. Defaults to (80, 20).\n    pub terminal_dimensions: (u16, u16),\n\n    /// If true, _(default: false)_, the cursor will be hidden for a more visually appealing display.\n    ///\n    /// Please note that you must make sure the line renderer is properly shut down to restore the previous cursor\n    /// settings. See the `signal-hook` documentation in the README for more information.\n    pub hide_cursor: bool,\n\n    /// If true, (default false), we will keep track of the previous progress state to derive\n    /// continuous throughput information from. Throughput will only show for units which have\n    /// explicitly enabled it, it is opt-in.\n    ///\n    /// This comes at the cost of additional memory and CPU time.\n    pub throughput: bool,\n\n    /// If set, specify all levels that should be shown. Otherwise all available levels are shown.\n    ///\n    /// This is useful to filter out high-noise lower level progress items in the tree.\n    pub level_filter: Option<RangeInclusive<progress::key::Level>>,\n\n    /// If set, progress will only actually be shown after the given duration. Log messages will always be shown without delay.\n    ///\n    /// This option can be useful to not enforce progress for short actions, causing it to flicker.\n    /// Please note that this won't affect display of messages, which are simply logged.\n    pub initial_delay: Option<Duration>,\n\n    /// The amount of frames to draw per second. If below 1.0, it determines the amount of seconds between the frame.\n    ///\n    /// *e.g.* 1.0/4.0 is one frame every 4 seconds.\n    pub frames_per_second: f32,\n\n    /// If true (default: true), we will keep waiting for progress even after we encountered an empty list of drawable progress items.\n    ///\n    /// Please note that you should add at least one item to the `prodash::Tree` before launching the application or else\n    /// risk a race causing nothing to be rendered at all.\n    pub keep_running_if_progress_is_empty: bool,\n}\n\n/// The kind of stream to use for auto-configuration.\npub enum StreamKind {\n    /// Standard output\n    Stdout,\n    /// Standard error\n    Stderr,\n}\n\n/// Convenience\nimpl Options {\n    /// Automatically configure (and overwrite) the following fields based on terminal configuration.\n    ///\n    /// * output_is_terminal\n    /// * colored\n    /// * terminal_dimensions\n    /// * hide-cursor (based on presence of 'signal-hook' feature.\n    #[cfg(feature = \"render-line-autoconfigure\")]\n    pub fn auto_configure(mut self, output: StreamKind) -> Self {\n        self.output_is_terminal = match output {\n            StreamKind::Stdout => is_terminal::is_terminal(std::io::stdout()),\n            StreamKind::Stderr => is_terminal::is_terminal(std::io::stderr()),\n        };\n        self.colored = self.output_is_terminal && crosstermion::color::allowed();\n        self.terminal_dimensions = crosstermion::terminal::size().unwrap_or((80, 20));\n        #[cfg(feature = \"signal-hook\")]\n        self.auto_hide_cursor();\n        self\n    }\n    #[cfg(all(feature = \"render-line-autoconfigure\", feature = \"signal-hook\"))]\n    fn auto_hide_cursor(&mut self) {\n        self.hide_cursor = true;\n    }\n    #[cfg(not(feature = \"render-line-autoconfigure\"))]\n    /// No-op - only available with the `render-line-autoconfigure` feature toggle.\n    pub fn auto_configure(self, _output: StreamKind) -> Self {\n        self\n    }\n}\n\nimpl Default for Options {\n    fn default() -> Self {\n        Options {\n            output_is_terminal: true,\n            colored: true,\n            timestamp: false,\n            terminal_dimensions: (80, 20),\n            hide_cursor: false,\n            level_filter: None,\n            initial_delay: None,\n            frames_per_second: 6.0,\n            throughput: false,\n            keep_running_if_progress_is_empty: true,\n        }\n    }\n}\n\n/// A handle to the render thread, which when dropped will instruct it to stop showing progress.\npub struct JoinHandle {\n    inner: Option<std::thread::JoinHandle<io::Result<()>>>,\n    connection: std::sync::mpsc::SyncSender<Event>,\n    // If we disconnect before sending a Quit event, the selector continuously informs about the 'Disconnect' state\n    disconnected: bool,\n}\n\nimpl JoinHandle {\n    /// `detach()` and `forget()` to remove any effects associated with this handle.\n    pub fn detach(mut self) {\n        self.disconnect();\n        self.forget();\n    }\n    /// Remove the handles capability to instruct the render thread to stop, but it will still wait for it\n    /// if dropped.\n    /// Use `forget()` if it should not wait for the render thread anymore.\n    pub fn disconnect(&mut self) {\n        self.disconnected = true;\n    }\n    /// Remove the handles capability to `join()` by forgetting the threads handle\n    pub fn forget(&mut self) {\n        self.inner.take();\n    }\n    /// Wait for the thread to shutdown naturally, for example because there is no more progress to display\n    pub fn wait(mut self) {\n        self.inner.take().and_then(|h| h.join().ok());\n    }\n    /// Send the shutdown signal right after one last redraw\n    pub fn shutdown(&mut self) {\n        if !self.disconnected {\n            self.connection.send(Event::Tick).ok();\n            self.connection.send(Event::Quit).ok();\n        }\n    }\n    /// Send the signal to shutdown and wait for the thread to be shutdown.\n    pub fn shutdown_and_wait(mut self) {\n        self.shutdown();\n        self.wait();\n    }\n}\n\nimpl Drop for JoinHandle {\n    fn drop(&mut self) {\n        self.shutdown();\n        self.inner.take().and_then(|h| h.join().ok());\n    }\n}\n\n#[derive(Debug)]\nenum Event {\n    Tick,\n    Quit,\n    #[cfg(feature = \"signal-hook\")]\n    Resize(u16, u16),\n}\n\n/// Write a line-based representation of `progress` to `out` which is assumed to be a terminal.\n///\n/// Configure it with `config`, see the [`Options`] for details.\npub fn render(\n    mut out: impl io::Write + Send + 'static,\n    progress: impl WeakRoot + Send + 'static,\n    Options {\n        output_is_terminal,\n        colored,\n        timestamp,\n        level_filter,\n        terminal_dimensions,\n        initial_delay,\n        frames_per_second,\n        keep_running_if_progress_is_empty,\n        hide_cursor,\n        throughput,\n    }: Options,\n) -> JoinHandle {\n    #[cfg_attr(not(feature = \"signal-hook\"), allow(unused_mut))]\n    let mut config = draw::Options {\n        level_filter,\n        terminal_dimensions,\n        keep_running_if_progress_is_empty,\n        output_is_terminal,\n        colored,\n        timestamp,\n        hide_cursor,\n    };\n\n    let (event_send, event_recv) = std::sync::mpsc::sync_channel::<Event>(1);\n    let show_cursor = possibly_hide_cursor(&mut out, hide_cursor && output_is_terminal);\n    static SHOW_PROGRESS: AtomicBool = AtomicBool::new(false);\n    #[cfg(feature = \"signal-hook\")]\n    let term_signal_received: Arc<AtomicBool> = Arc::new(AtomicBool::new(false));\n    #[cfg(feature = \"signal-hook\")]\n    let terminal_resized: Arc<AtomicBool> = Arc::new(AtomicBool::new(false));\n    #[cfg(feature = \"signal-hook\")]\n    {\n        for sig in signal_hook::consts::TERM_SIGNALS {\n            signal_hook::flag::register(*sig, term_signal_received.clone()).ok();\n        }\n\n        #[cfg(unix)]\n        signal_hook::flag::register(signal_hook::consts::SIGWINCH, terminal_resized.clone()).ok();\n    }\n\n    let handle = std::thread::Builder::new()\n        .name(\"render-line-eventloop\".into())\n        .spawn({\n            let tick_send = event_send.clone();\n            move || {\n                {\n                    let initial_delay = initial_delay.unwrap_or_default();\n                    SHOW_PROGRESS.store(initial_delay == Duration::default(), Ordering::Relaxed);\n                    if !SHOW_PROGRESS.load(Ordering::Relaxed) {\n                        std::thread::Builder::new()\n                            .name(\"render-line-progress-delay\".into())\n                            .spawn(move || {\n                                std::thread::sleep(initial_delay);\n                                SHOW_PROGRESS.store(true, Ordering::Relaxed);\n                            })\n                            .ok();\n                    }\n                }\n\n                let mut state = draw::State::default();\n                if throughput {\n                    state.throughput = Some(Throughput::default());\n                }\n                let secs = 1.0 / frames_per_second;\n                let _ticker = std::thread::Builder::new()\n                    .name(\"render-line-ticker\".into())\n                    .spawn(move || loop {\n                        #[cfg(feature = \"signal-hook\")]\n                        {\n                            if term_signal_received.load(Ordering::SeqCst) {\n                                tick_send.send(Event::Quit).ok();\n                                break;\n                            }\n                            if terminal_resized.load(Ordering::SeqCst) {\n                                terminal_resized.store(false, Ordering::SeqCst);\n                                if let Ok((x, y)) = crosstermion::terminal::size() {\n                                    tick_send.send(Event::Resize(x, y)).ok();\n                                }\n                            }\n                        }\n                        if tick_send.send(Event::Tick).is_err() {\n                            break;\n                        }\n                        std::thread::sleep(Duration::from_secs_f32(secs));\n                    })\n                    .expect(\"starting a thread works\");\n\n                for event in event_recv {\n                    match event {\n                        #[cfg(feature = \"signal-hook\")]\n                        Event::Resize(x, y) => {\n                            config.terminal_dimensions = (x, y);\n                            draw::all(&mut out, SHOW_PROGRESS.load(Ordering::Relaxed), &mut state, &config)?;\n                        }\n                        Event::Tick => match progress.upgrade() {\n                            Some(progress) => {\n                                let has_changed = state.update_from_progress(&progress);\n                                draw::all(\n                                    &mut out,\n                                    SHOW_PROGRESS.load(Ordering::Relaxed) && has_changed,\n                                    &mut state,\n                                    &config,\n                                )?;\n                            }\n                            None => {\n                                state.clear();\n                                draw::all(&mut out, SHOW_PROGRESS.load(Ordering::Relaxed), &mut state, &config)?;\n                                break;\n                            }\n                        },\n                        Event::Quit => {\n                            state.clear();\n                            draw::all(&mut out, SHOW_PROGRESS.load(Ordering::Relaxed), &mut state, &config)?;\n                            break;\n                        }\n                    }\n                }\n\n                if show_cursor {\n                    crosstermion::execute!(out, crosstermion::cursor::Show).ok();\n                }\n\n                // One day we might try this out on windows, but let's not risk it now.\n                #[cfg(unix)]\n                write!(out, \"\\x1b[2K\\r\").ok(); // clear the last line.\n                Ok(())\n            }\n        })\n        .expect(\"starting a thread works\");\n\n    JoinHandle {\n        inner: Some(handle),\n        connection: event_send,\n        disconnected: false,\n    }\n}\n\n// Not all configurations actually need it to be mut, but those with the 'signal-hook' feature do\n#[allow(unused_mut)]\nfn possibly_hide_cursor(out: &mut impl io::Write, mut hide_cursor: bool) -> bool {\n    if hide_cursor {\n        crosstermion::execute!(out, crosstermion::cursor::Hide).is_ok()\n    } else {\n        false\n    }\n}\n"
  },
  {
    "path": "src/render/line/mod.rs",
    "content": "#[cfg(all(feature = \"render-line\", not(any(feature = \"render-line-crossterm\"))))]\ncompile_error!(\"Please use the 'render-line-crossterm' feature\");\n\nmod draw;\nmod engine;\n\npub use engine::{render, JoinHandle, Options, StreamKind};\n"
  },
  {
    "path": "src/render/mod.rs",
    "content": "#[cfg(feature = \"render-tui\")]\n///\npub mod tui;\n#[cfg(feature = \"render-tui\")]\npub use self::tui::render as tui;\n\n#[cfg(feature = \"render-line\")]\n///\npub mod line;\n#[cfg(feature = \"render-line\")]\npub use self::line::render as line;\n"
  },
  {
    "path": "src/render/tui/draw/all.rs",
    "content": "use std::time::Duration;\n\nuse tui::{\n    buffer::Buffer,\n    layout::Rect,\n    style::{Modifier, Style},\n    text::Span,\n    widgets::{Block, Borders, Widget},\n};\n\nuse crate::{\n    messages::Message,\n    progress::{Key, Task},\n    render::tui::{\n        draw,\n        utils::{block_width, rect},\n        InterruptDrawInfo, Line,\n    },\n    Throughput,\n};\n\n#[derive(Default)]\npub struct State {\n    pub title: String,\n    pub task_offset: u16,\n    pub message_offset: u16,\n    pub hide_messages: bool,\n    pub messages_fullscreen: bool,\n    pub user_provided_window_size: Option<Rect>,\n    pub duration_per_frame: Duration,\n    pub information: Vec<Line>,\n    pub hide_info: bool,\n    pub maximize_info: bool,\n    pub last_tree_column_width: Option<u16>,\n    pub next_tree_column_width: Option<u16>,\n    pub throughput: Option<Throughput>,\n}\n\npub(crate) fn all(\n    state: &mut State,\n    interrupt_mode: InterruptDrawInfo,\n    entries: &[(Key, Task)],\n    messages: &[Message],\n    bound: Rect,\n    buf: &mut Buffer,\n) {\n    let (bound, info_pane) = compute_info_bound(\n        bound,\n        if state.hide_info { &[] } else { &state.information },\n        state.maximize_info,\n    );\n    let bold = Style::default().add_modifier(Modifier::BOLD);\n    let window = Block::default()\n        .title(Span::styled(state.title.as_str(), bold))\n        .borders(Borders::ALL);\n    let inner_area = window.inner(bound);\n    window.render(bound, buf);\n    if bound.width < 4 || bound.height < 4 {\n        return;\n    }\n\n    let border_width = 1;\n    draw::progress::headline(\n        entries,\n        interrupt_mode,\n        state.duration_per_frame,\n        buf,\n        rect::offset_x(\n            Rect {\n                height: 1,\n                width: bound.width.saturating_sub(border_width),\n                ..bound\n            },\n            block_width(&state.title) + (border_width * 2),\n        ),\n    );\n\n    let (progress_pane, messages_pane) = compute_pane_bounds(\n        if state.hide_messages { &[] } else { messages },\n        inner_area,\n        state.messages_fullscreen,\n    );\n\n    draw::progress::pane(entries, progress_pane, buf, state);\n    if let Some(messages_pane) = messages_pane {\n        draw::messages::pane(\n            messages,\n            messages_pane,\n            Rect {\n                width: messages_pane.width + 2,\n                ..rect::line_bound(bound, bound.height.saturating_sub(1) as usize)\n            },\n            &mut state.message_offset,\n            buf,\n        );\n    }\n\n    if let Some(info_pane) = info_pane {\n        draw::information::pane(&state.information, info_pane, buf);\n    }\n}\n\nfn compute_pane_bounds(messages: &[Message], inner: Rect, messages_fullscreen: bool) -> (Rect, Option<Rect>) {\n    if messages.is_empty() {\n        (inner, None)\n    } else {\n        let (task_percent, messages_percent) = if messages_fullscreen { (0.1, 0.9) } else { (0.75, 0.25) };\n        let tasks_height: u16 = (inner.height as f32 * task_percent).ceil() as u16;\n        let messages_height: u16 = (inner.height as f32 * messages_percent).floor() as u16;\n        if messages_height < 2 {\n            (inner, None)\n        } else {\n            let messages_title = 1u16;\n            let new_messages_height = messages_height.min((messages.len() + messages_title as usize) as u16);\n            let tasks_height = tasks_height.saturating_add(messages_height - new_messages_height);\n            let messages_height = new_messages_height;\n            (\n                Rect {\n                    height: tasks_height,\n                    ..inner\n                },\n                Some(rect::intersect(\n                    Rect {\n                        y: tasks_height + messages_title,\n                        height: messages_height,\n                        ..inner\n                    },\n                    inner,\n                )),\n            )\n        }\n    }\n}\n\nfn compute_info_bound(bound: Rect, info: &[Line], maximize: bool) -> (Rect, Option<Rect>) {\n    if info.is_empty() {\n        return (bound, None);\n    }\n    let margin = 1;\n    let max_line_width = info.iter().fold(0, |state, l| {\n        state.max(\n            block_width(match l {\n                Line::Text(s) | Line::Title(s) => s,\n            }) + margin * 2,\n        )\n    });\n    let pane_width = if maximize {\n        bound.width.saturating_sub(8).min(max_line_width)\n    } else {\n        (bound.width / 3).min(max_line_width)\n    };\n\n    if pane_width < max_line_width / 3 {\n        return (bound, None);\n    }\n\n    (\n        Rect {\n            width: bound.width.saturating_sub(pane_width),\n            ..bound\n        },\n        Some(rect::snap_to_right(bound, pane_width)),\n    )\n}\n"
  },
  {
    "path": "src/render/tui/draw/information.rs",
    "content": "use tui::{\n    buffer::Buffer,\n    layout::Rect,\n    style::{Modifier, Style},\n    text::Span,\n    widgets::{Block, Borders, Widget},\n};\n\nuse crate::render::tui::{\n    utils::{block_width, draw_text_with_ellipsis_nowrap, rect},\n    Line,\n};\n\npub fn pane(lines: &[Line], bound: Rect, buf: &mut Buffer) {\n    let bold = Style::default().add_modifier(Modifier::BOLD);\n    let block = Block::default()\n        .title(Span::styled(\"Information\", bold))\n        .borders(Borders::TOP | Borders::BOTTOM);\n    let inner_bound = block.inner(bound);\n    block.render(bound, buf);\n\n    let help_text = \" ⨯ = [ | ▢ = { \";\n    draw_text_with_ellipsis_nowrap(rect::snap_to_right(bound, block_width(help_text)), buf, help_text, bold);\n\n    let bound = Rect {\n        width: inner_bound.width.saturating_sub(1),\n        ..inner_bound\n    };\n    let mut offset = 0;\n    for (line, info) in lines.windows(2).enumerate() {\n        let (info, next_info) = (&info[0], &info[1]);\n        let line = line + offset;\n        if line >= bound.height as usize {\n            break;\n        }\n        let line_bound = rect::line_bound(bound, line);\n        match info {\n            Line::Title(text) => {\n                let blocks_drawn = draw_text_with_ellipsis_nowrap(line_bound, buf, text, bold);\n                let lines_rect = rect::offset_x(line_bound, blocks_drawn + 1);\n                for x in lines_rect.left()..lines_rect.right() {\n                    buf[(x, lines_rect.y)].set_symbol(\"─\");\n                }\n                offset += 1;\n            }\n            Line::Text(text) => {\n                draw_text_with_ellipsis_nowrap(rect::offset_x(line_bound, 1), buf, text, None);\n            }\n        };\n        if let Line::Title(_) = next_info {\n            offset += 1;\n        }\n    }\n\n    if let Some(Line::Text(text)) = lines.last() {\n        let line = lines.len().saturating_sub(1) + offset;\n        if line < bound.height as usize {\n            draw_text_with_ellipsis_nowrap(rect::offset_x(rect::line_bound(bound, line), 1), buf, text, bold);\n        }\n    }\n}\n"
  },
  {
    "path": "src/render/tui/draw/messages.rs",
    "content": "use std::time::SystemTime;\n\nuse tui::{\n    buffer::Buffer,\n    layout::Rect,\n    style::{Color, Modifier, Style},\n    text::Span,\n    widgets::{Block, Borders, Widget},\n};\nuse unicode_width::UnicodeWidthStr;\n\nuse crate::{\n    messages::{Message, MessageLevel},\n    render::tui::utils::{block_width, draw_text_with_ellipsis_nowrap, rect, sanitize_offset, VERTICAL_LINE},\n    time::{format_time_for_messages, DATE_TIME_HMS},\n};\n\npub fn pane(messages: &[Message], bound: Rect, overflow_bound: Rect, offset: &mut u16, buf: &mut Buffer) {\n    let bold = Style::default().add_modifier(Modifier::BOLD);\n    let block = Block::default()\n        .title(Span::styled(\"Messages\", bold))\n        .borders(Borders::TOP);\n    let inner_bound = block.inner(bound);\n    block.render(bound, buf);\n    let help_text = \" ⨯ = `| ▢ = ~ \";\n    draw_text_with_ellipsis_nowrap(rect::snap_to_right(bound, block_width(help_text)), buf, help_text, bold);\n\n    let bound = inner_bound;\n    *offset = sanitize_offset(*offset, messages.len(), bound.height);\n    let max_origin_width = messages\n        .iter()\n        .rev()\n        .skip(*offset as usize)\n        .take(bound.height as usize)\n        .fold(0, |state, message| state.max(block_width(&message.origin)));\n    for (\n        line,\n        Message {\n            time,\n            message,\n            level,\n            origin,\n        },\n    ) in messages\n        .iter()\n        .rev()\n        .skip(*offset as usize)\n        .take(bound.height as usize)\n        .enumerate()\n    {\n        let line_bound = rect::line_bound(bound, line);\n        let (time_bound, level_bound, origin_bound, message_bound) = compute_bounds(line_bound, max_origin_width);\n        if let Some(time_bound) = time_bound {\n            draw_text_with_ellipsis_nowrap(time_bound, buf, format_time_column(time), None);\n        }\n        if let Some(level_bound) = level_bound {\n            draw_text_with_ellipsis_nowrap(\n                level_bound,\n                buf,\n                format_level_column(*level),\n                Some(level_to_style(*level)),\n            );\n            draw_text_with_ellipsis_nowrap(rect::offset_x(level_bound, LEVEL_TEXT_WIDTH), buf, VERTICAL_LINE, None);\n        }\n        if let Some(origin_bound) = origin_bound {\n            draw_text_with_ellipsis_nowrap(origin_bound, buf, origin, None);\n            draw_text_with_ellipsis_nowrap(rect::offset_x(origin_bound, max_origin_width), buf, \"→\", None);\n        }\n        draw_text_with_ellipsis_nowrap(message_bound, buf, message, None);\n    }\n\n    if (bound.height as usize) < messages.len().saturating_sub(*offset as usize)\n        || (*offset).min(messages.len() as u16) > 0\n    {\n        let messages_below = messages\n            .len()\n            .saturating_sub(bound.height.saturating_add(*offset) as usize);\n        let messages_skipped = (*offset).min(messages.len() as u16);\n        draw_text_with_ellipsis_nowrap(\n            rect::offset_x(overflow_bound, 1),\n            buf,\n            format!(\"… {} skipped and {} more\", messages_skipped, messages_below),\n            bold,\n        );\n        let help_text = \" ⇊ = D|↓ = J|⇈ = U|↑ = K ┘\";\n        draw_text_with_ellipsis_nowrap(\n            rect::snap_to_right(overflow_bound, block_width(help_text)),\n            buf,\n            help_text,\n            bold,\n        );\n    }\n}\n\nconst LEVEL_TEXT_WIDTH: u16 = 4;\nfn format_level_column(level: MessageLevel) -> &'static str {\n    use MessageLevel::*;\n    match level {\n        Info => \"info\",\n        Failure => \"fail\",\n        Success => \"done\",\n    }\n}\n\nfn level_to_style(level: MessageLevel) -> Style {\n    use MessageLevel::*;\n    Style::default()\n        .fg(Color::Black)\n        .add_modifier(Modifier::BOLD)\n        .bg(match level {\n            Info => Color::White,\n            Failure => Color::Red,\n            Success => Color::Green,\n        })\n}\n\nfn format_time_column(time: &SystemTime) -> String {\n    format!(\"{}{}\", format_time_for_messages(*time), VERTICAL_LINE)\n}\n\nfn compute_bounds(line: Rect, max_origin_width: u16) -> (Option<Rect>, Option<Rect>, Option<Rect>, Rect) {\n    let vertical_line_width = VERTICAL_LINE.width() as u16;\n    let mythical_offset_we_should_not_need = 1;\n\n    let time_bound = Rect {\n        width: DATE_TIME_HMS as u16 + vertical_line_width,\n        ..line\n    };\n\n    let mut cursor = time_bound.width + mythical_offset_we_should_not_need;\n    let level_bound = Rect {\n        x: cursor,\n        width: LEVEL_TEXT_WIDTH + vertical_line_width,\n        ..line\n    };\n    cursor += level_bound.width;\n\n    let origin_bound = Rect {\n        x: cursor,\n        width: max_origin_width + vertical_line_width,\n        ..line\n    };\n    cursor += origin_bound.width;\n\n    let message_bound = rect::intersect(rect::offset_x(line, cursor), line);\n    if message_bound.width < 30 {\n        return (None, None, None, line);\n    }\n    (Some(time_bound), Some(level_bound), Some(origin_bound), message_bound)\n}\n"
  },
  {
    "path": "src/render/tui/draw/mod.rs",
    "content": "mod all;\nmod information;\nmod messages;\nmod progress;\n\npub(crate) use all::{all, State};\n"
  },
  {
    "path": "src/render/tui/draw/progress.rs",
    "content": "use std::{fmt, sync::atomic::Ordering, time::Duration};\n\nuse tui::{\n    buffer::Buffer,\n    layout::Rect,\n    style::{Color, Modifier, Style},\n};\nuse tui_react::fill_background;\n\nuse crate::{\n    progress::{self, Key, Step, Task, Value},\n    render::tui::{\n        draw::State,\n        utils::{\n            block_width, draw_text_nowrap_fn, draw_text_with_ellipsis_nowrap, rect, sanitize_offset,\n            GraphemeCountWriter, VERTICAL_LINE,\n        },\n        InterruptDrawInfo,\n    },\n    time::format_now_datetime_seconds,\n    unit, Throughput,\n};\n\nconst MIN_TREE_WIDTH: u16 = 20;\n\npub fn pane(entries: &[(Key, progress::Task)], mut bound: Rect, buf: &mut Buffer, state: &mut State) {\n    state.task_offset = sanitize_offset(state.task_offset, entries.len(), bound.height);\n    let needs_overflow_line =\n        if entries.len() > bound.height as usize || (state.task_offset).min(entries.len() as u16) > 0 {\n            bound.height = bound.height.saturating_sub(1);\n            true\n        } else {\n            false\n        };\n    state.task_offset = sanitize_offset(state.task_offset, entries.len(), bound.height);\n\n    if entries.is_empty() {\n        return;\n    }\n\n    let initial_column_width = bound.width / 3;\n    let desired_max_tree_draw_width = *state.next_tree_column_width.as_ref().unwrap_or(&initial_column_width);\n    {\n        if initial_column_width >= MIN_TREE_WIDTH {\n            let tree_bound = Rect {\n                width: desired_max_tree_draw_width,\n                ..bound\n            };\n            let computed = draw_tree(entries, buf, tree_bound, state.task_offset);\n            state.last_tree_column_width = Some(computed);\n        } else {\n            state.last_tree_column_width = Some(0);\n        };\n    }\n\n    {\n        if let Some(tp) = state.throughput.as_mut() {\n            tp.update_elapsed();\n        }\n\n        let progress_area = rect::offset_x(bound, desired_max_tree_draw_width);\n        draw_progress(\n            entries,\n            buf,\n            progress_area,\n            state.task_offset,\n            state.throughput.as_mut(),\n        );\n\n        if let Some(tp) = state.throughput.as_mut() {\n            tp.reconcile(entries);\n        }\n    }\n\n    if needs_overflow_line {\n        let overflow_rect = Rect {\n            y: bound.height + 1,\n            height: 1,\n            ..bound\n        };\n        draw_overflow(\n            entries,\n            buf,\n            overflow_rect,\n            desired_max_tree_draw_width,\n            bound.height,\n            state.task_offset,\n        );\n    }\n}\n\npub(crate) fn headline(\n    entries: &[(Key, Task)],\n    interrupt_mode: InterruptDrawInfo,\n    duration_per_frame: Duration,\n    buf: &mut Buffer,\n    bound: Rect,\n) {\n    let (num_running_tasks, num_blocked_tasks, num_groups) = entries.iter().fold(\n        (0, 0, 0),\n        |(mut running, mut blocked, mut groups), (_key, Task { progress, .. })| {\n            match progress.as_ref().map(|p| p.state) {\n                Some(progress::State::Running) => running += 1,\n                Some(progress::State::Blocked(_, _)) | Some(progress::State::Halted(_, _)) => blocked += 1,\n                None => groups += 1,\n            }\n            (running, blocked, groups)\n        },\n    );\n    let text = format!(\n        \" {} {} {:3} running + {:3} blocked + {:3} groups = {} \",\n        match interrupt_mode {\n            InterruptDrawInfo::Instantly => \"'q' or CTRL+c to quit\",\n            InterruptDrawInfo::Deferred(interrupt_requested) => {\n                if interrupt_requested {\n                    \"interrupt requested - please wait\"\n                } else {\n                    \"cannot interrupt current operation\"\n                }\n            }\n        },\n        if duration_per_frame > Duration::from_secs(1) {\n            format!(\n                \" Every {}s → {}\",\n                duration_per_frame.as_secs(),\n                format_now_datetime_seconds()\n            )\n        } else {\n            \"\".into()\n        },\n        num_running_tasks,\n        num_blocked_tasks,\n        num_groups,\n        entries.len()\n    );\n\n    let bold = Style::default().add_modifier(Modifier::BOLD);\n    draw_text_with_ellipsis_nowrap(rect::snap_to_right(bound, block_width(&text) + 1), buf, text, bold);\n}\n\nstruct ProgressFormat<'a>(&'a Option<Value>, u16, Option<unit::display::Throughput>);\n\nimpl fmt::Display for ProgressFormat<'_> {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self.0 {\n            Some(p) => match p.unit.as_ref() {\n                Some(unit) => write!(\n                    f,\n                    \"{}\",\n                    unit.display(p.step.load(Ordering::SeqCst), p.done_at, self.2.clone())\n                ),\n                None => match p.done_at {\n                    Some(done_at) => write!(f, \"{}/{}\", p.step.load(Ordering::SeqCst), done_at),\n                    None => write!(f, \"{}\", p.step.load(Ordering::SeqCst)),\n                },\n            },\n            None => write!(f, \"{:─<width$}\", '─', width = self.1 as usize),\n        }\n    }\n}\n\nfn has_child(entries: &[(Key, Task)], index: usize) -> bool {\n    entries\n        .get(index + 1)\n        .and_then(|(other_key, other_val)| {\n            entries.get(index).map(|(cur_key, _)| {\n                cur_key.shares_parent_with(other_key, cur_key.level()) && other_val.progress.is_some()\n            })\n        })\n        .unwrap_or(false)\n}\n\npub fn draw_progress(\n    entries: &[(Key, Task)],\n    buf: &mut Buffer,\n    bound: Rect,\n    offset: u16,\n    mut throughput: Option<&mut Throughput>,\n) {\n    let title_spacing = 2u16 + 1; // 2 on the left, 1 on the right\n    let max_progress_label_width = entries\n        .iter()\n        .skip(offset as usize)\n        .take(bound.height as usize)\n        .map(|(_, Task { progress, .. })| progress)\n        .fold(0, |state, progress| match progress {\n            progress @ Some(_) => {\n                use std::io::Write;\n                let mut w = GraphemeCountWriter::default();\n                write!(w, \"{}\", ProgressFormat(progress, 0, None)).expect(\"never fails\");\n                state.max(w.0)\n            }\n            None => state,\n        });\n\n    for (\n        line,\n        (\n            entry_index,\n            (\n                key,\n                Task {\n                    progress,\n                    name: title,\n                    id: _,\n                },\n            ),\n        ),\n    ) in entries\n        .iter()\n        .enumerate()\n        .skip(offset as usize)\n        .take(bound.height as usize)\n        .enumerate()\n    {\n        let throughput = throughput\n            .as_mut()\n            .and_then(|tp| tp.update_and_get(key, progress.as_ref()));\n        let line_bound = rect::line_bound(bound, line);\n        let progress_text = format!(\n            \" {progress}\",\n            progress = ProgressFormat(\n                progress,\n                if has_child(entries, entry_index) {\n                    bound.width.saturating_sub(title_spacing)\n                } else {\n                    0\n                },\n                throughput\n            )\n        );\n\n        draw_text_with_ellipsis_nowrap(line_bound, buf, VERTICAL_LINE, None);\n\n        let tree_prefix = level_prefix(entries, entry_index);\n        let progress_rect = rect::offset_x(line_bound, block_width(&tree_prefix));\n        draw_text_with_ellipsis_nowrap(line_bound, buf, tree_prefix, None);\n        match progress\n            .as_ref()\n            .map(|p| (p.fraction(), p.state, p.step.load(Ordering::SeqCst)))\n        {\n            Some((Some(fraction), state, _step)) => {\n                let mut progress_text = progress_text;\n                add_block_eta(state, &mut progress_text);\n                let (bound, style) = draw_progress_bar_fn(buf, progress_rect, fraction, |fraction| match state {\n                    progress::State::Blocked(_, _) => Color::Red,\n                    progress::State::Halted(_, _) => Color::LightRed,\n                    progress::State::Running => {\n                        if fraction >= 0.8 {\n                            Color::Green\n                        } else {\n                            Color::Yellow\n                        }\n                    }\n                });\n                let style_fn = move |_t: &str, x: u16, _y: u16| {\n                    if x < bound.right() {\n                        style\n                    } else {\n                        Style::default()\n                    }\n                };\n                draw_text_nowrap_fn(progress_rect, buf, progress_text, style_fn);\n            }\n            Some((None, state, step)) => {\n                let mut progress_text = progress_text;\n                add_block_eta(state, &mut progress_text);\n                draw_text_with_ellipsis_nowrap(progress_rect, buf, progress_text, None);\n                let bar_rect = rect::offset_x(line_bound, max_progress_label_width as u16);\n                draw_spinner(\n                    buf,\n                    bar_rect,\n                    step,\n                    line,\n                    match state {\n                        progress::State::Blocked(_, _) => Color::Red,\n                        progress::State::Halted(_, _) => Color::LightRed,\n                        progress::State::Running => Color::White,\n                    },\n                );\n            }\n            None => {\n                let bold = Style::default().add_modifier(Modifier::BOLD);\n                draw_text_nowrap_fn(progress_rect, buf, progress_text, |_, _, _| Style::default());\n                draw_text_with_ellipsis_nowrap(progress_rect, buf, format!(\" {} \", title), bold);\n            }\n        }\n    }\n}\n\nfn add_block_eta(state: progress::State, progress_text: &mut String) {\n    match state {\n        progress::State::Blocked(reason, maybe_eta) | progress::State::Halted(reason, maybe_eta) => {\n            progress_text.push_str(\" [\");\n            progress_text.push_str(reason);\n            progress_text.push(']');\n            if let Some(eta) = maybe_eta {\n                let eta = jiff::Timestamp::try_from(eta).expect(\"reasonable system time\");\n                let now = jiff::Timestamp::now();\n                if eta > now {\n                    use std::fmt::Write;\n                    write!(\n                        progress_text,\n                        \" → {:#} to {}\",\n                        eta.duration_since(now),\n                        if let progress::State::Blocked(_, _) = state {\n                            \"unblock\"\n                        } else {\n                            \"continue\"\n                        }\n                    )\n                    .expect(\"in-memory writes never fail\");\n                }\n            }\n        }\n        progress::State::Running => {}\n    }\n}\n\nfn draw_spinner(buf: &mut Buffer, bound: Rect, step: Step, seed: usize, color: Color) {\n    if bound.width == 0 {\n        return;\n    }\n    let x = bound.x + ((step + seed) % bound.width as usize) as u16;\n    let width = 5;\n    let bound = rect::intersect(Rect { x, width, ..bound }, bound);\n    tui_react::fill_background(bound, buf, color);\n}\n\nfn draw_progress_bar_fn(\n    buf: &mut Buffer,\n    bound: Rect,\n    fraction: f32,\n    style: impl FnOnce(f32) -> Color,\n) -> (Rect, Style) {\n    if bound.width == 0 {\n        return (Rect::default(), Style::default());\n    }\n    let mut fractional_progress_rect = Rect {\n        width: ((bound.width as f32 * fraction).floor() as u16).min(bound.width),\n        ..bound\n    };\n    let color = style(fraction);\n    for y in fractional_progress_rect.top()..fractional_progress_rect.bottom() {\n        for x in fractional_progress_rect.left()..fractional_progress_rect.right() {\n            let cell = &mut buf[(x, y)];\n            cell.set_fg(color);\n            cell.set_symbol(tui::symbols::block::FULL);\n        }\n    }\n    if fractional_progress_rect.width < bound.width {\n        static BLOCK_SECTIONS: [&str; 9] = [\n            \" \",\n            tui::symbols::block::ONE_EIGHTH,\n            tui::symbols::block::ONE_QUARTER,\n            tui::symbols::block::THREE_EIGHTHS,\n            tui::symbols::block::HALF,\n            tui::symbols::block::FIVE_EIGHTHS,\n            tui::symbols::block::THREE_QUARTERS,\n            tui::symbols::block::SEVEN_EIGHTHS,\n            tui::symbols::block::FULL,\n        ];\n        // Get the index based on how filled the remaining part is\n        let index = ((((bound.width as f32 * fraction) - fractional_progress_rect.width as f32) * 8f32).round()\n            as usize)\n            % BLOCK_SECTIONS.len();\n        let cell = &mut buf[(fractional_progress_rect.right(), bound.y)];\n        cell.set_symbol(BLOCK_SECTIONS[index]);\n        cell.set_fg(color);\n        fractional_progress_rect.width += 1;\n    }\n    (fractional_progress_rect, Style::default().bg(color).fg(Color::Black))\n}\n\npub fn draw_tree(entries: &[(Key, Task)], buf: &mut Buffer, bound: Rect, offset: u16) -> u16 {\n    let mut max_prefix_len = 0;\n    for (line, (entry_index, entry)) in entries\n        .iter()\n        .enumerate()\n        .skip(offset as usize)\n        .take(bound.height as usize)\n        .enumerate()\n    {\n        let mut line_bound = rect::line_bound(bound, line);\n        line_bound.x = line_bound.x.saturating_sub(1);\n        line_bound.width = line_bound.width.saturating_sub(1);\n        let tree_prefix = format!(\"{} {} \", level_prefix(entries, entry_index), entry.1.name);\n        max_prefix_len = max_prefix_len.max(block_width(&tree_prefix));\n\n        let style = if entry.1.progress.is_none() {\n            Style::default().add_modifier(Modifier::BOLD).into()\n        } else {\n            None\n        };\n        draw_text_with_ellipsis_nowrap(line_bound, buf, tree_prefix, style);\n    }\n    max_prefix_len\n}\n\nfn level_prefix(entries: &[(Key, Task)], entry_index: usize) -> String {\n    let adj = Key::adjacency(entries, entry_index);\n    let key = entries[entry_index].0;\n    let key_level = key.level();\n    let is_orphan = adj.level() != key_level;\n    let mut buf = String::with_capacity(key_level as usize);\n    for level in 1..=key_level {\n        use crate::progress::key::SiblingLocation::*;\n        let is_child_level = level == key_level;\n        if level != 1 {\n            buf.push(' ');\n        }\n        if level == 1 && is_child_level {\n            buf.push(match adj[level] {\n                AboveAndBelow | Above => '├',\n                NotFound | Below => '│',\n            });\n        } else {\n            let c = if is_child_level {\n                match adj[level] {\n                    NotFound => {\n                        if is_orphan {\n                            ' '\n                        } else {\n                            '·'\n                        }\n                    }\n                    Above => '└',\n                    Below => '┌',\n                    AboveAndBelow => '├',\n                }\n            } else {\n                match adj[level] {\n                    NotFound => {\n                        if level == 1 {\n                            '│'\n                        } else if is_orphan {\n                            '·'\n                        } else {\n                            ' '\n                        }\n                    }\n                    Above => '└',\n                    Below => '┌',\n                    AboveAndBelow => '│',\n                }\n            };\n            buf.push(c)\n        }\n    }\n    buf\n}\n\npub fn draw_overflow(\n    entries: &[(Key, Task)],\n    buf: &mut Buffer,\n    bound: Rect,\n    label_offset: u16,\n    num_entries_on_display: u16,\n    offset: u16,\n) {\n    let (count, mut progress_fraction) = entries\n        .iter()\n        .take(offset as usize)\n        .chain(entries.iter().skip((offset + num_entries_on_display) as usize))\n        .fold((0usize, 0f32), |(count, progress_fraction), (_key, value)| {\n            let progress = value.progress.as_ref().and_then(|p| p.fraction()).unwrap_or_default();\n            (count + 1, progress_fraction + progress)\n        });\n    progress_fraction /= count as f32;\n    let label = format!(\n        \"{} …{} skipped and {} more\",\n        if label_offset == 0 { \"\" } else { VERTICAL_LINE },\n        offset,\n        entries\n            .len()\n            .saturating_sub((offset + num_entries_on_display + 1) as usize)\n    );\n    let (progress_rect, style) = draw_progress_bar_fn(buf, bound, progress_fraction, |_| Color::Green);\n\n    let bg_color = Color::Red;\n    fill_background(rect::offset_x(bound, progress_rect.right() - 1), buf, bg_color);\n    let color_text_according_to_progress = move |_g: &str, x: u16, _y: u16| {\n        if x < progress_rect.right() {\n            style\n        } else {\n            style.bg(bg_color)\n        }\n    };\n    draw_text_nowrap_fn(\n        rect::offset_x(bound, label_offset),\n        buf,\n        label,\n        color_text_according_to_progress,\n    );\n    let help_text = \"⇊ = d|↓ = j|⇈ = u|↑ = k \";\n    draw_text_nowrap_fn(\n        rect::snap_to_right(bound, block_width(help_text)),\n        buf,\n        help_text,\n        color_text_according_to_progress,\n    );\n}\n"
  },
  {
    "path": "src/render/tui/engine.rs",
    "content": "use std::{\n    io::{self, Write},\n    time::Duration,\n};\n\nuse futures_lite::StreamExt;\nuse tui::layout::Rect;\n\nuse crate::{\n    render::tui::{draw, ticker},\n    Root, Throughput, WeakRoot,\n};\n\n/// Configure the terminal user interface\n#[derive(Clone)]\npub struct Options {\n    /// The initial title to show for the whole window.\n    ///\n    /// Can be adjusted later by sending `Event::SetTitle(…)`\n    /// into the event stream, see see [`tui::render_with_input(…events)`](./fn.render_with_input.html) function.\n    pub title: String,\n    /// The amount of frames to draw per second. If below 1.0, it determines the amount of seconds between the frame.\n    ///\n    /// *e.g.* 1.0/4.0 is one frame every 4 seconds.\n    pub frames_per_second: f32,\n\n    /// If true, (default false), we will keep track of the previous progress state to derive\n    /// continuous throughput information from. Throughput will only show for units which have\n    /// explicitly enabled it, it is opt-in.\n    ///\n    /// This comes at the cost of additional memory and CPU time.\n    pub throughput: bool,\n\n    /// If set, recompute the column width of the task tree only every given frame. Otherwise the width will be recomputed every frame.\n    ///\n    /// Use this if there are many short-running tasks with varying names paired with high refresh rates of multiple frames per second to\n    /// stabilize the appearance of the TUI.\n    ///\n    /// 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\n    /// every 2 seconds.\n    pub recompute_column_width_every_nth_frame: Option<usize>,\n    /// The initial window size.\n    ///\n    /// If unset, it will be retrieved from the current terminal.\n    pub window_size: Option<Rect>,\n\n    /// If true (default: true), we will stop running the TUI once the progress isn't available anymore (went out of scope).\n    pub stop_if_progress_missing: bool,\n}\n\nimpl Default for Options {\n    fn default() -> Self {\n        Options {\n            title: \"Progress Dashboard\".into(),\n            frames_per_second: 10.0,\n            throughput: false,\n            recompute_column_width_every_nth_frame: None,\n            window_size: None,\n            stop_if_progress_missing: true,\n        }\n    }\n}\n\n/// A line as used in [`Event::SetInformation`](./enum.Event.html#variant.SetInformation)\n#[derive(Debug, Clone, Eq, PartialEq)]\npub enum Line {\n    /// Set a title with the given text\n    Title(String),\n    /// Set a line of text with the given content\n    Text(String),\n}\n\n/// The variants represented here allow the user to control when the GUI can be shutdown.\n#[derive(Debug, Clone, Copy)]\npub enum Interrupt {\n    /// Immediately exit the GUI event loop when there is an interrupt request.\n    ///\n    /// This is the default when the event loop is entered.\n    Instantly,\n    /// Instead of exiting the event loop instantly, wait until the next Interrupt::Instantly\n    /// event is coming in.\n    Deferred,\n}\n\n#[derive(Clone, Copy)]\npub(crate) enum InterruptDrawInfo {\n    Instantly,\n    /// Boolean signals if interrupt is requested\n    Deferred(bool),\n}\n\n#[cfg(not(any(feature = \"render-tui-crossterm\")))]\ncompile_error!(\"Please set the 'render-tui-crossterm' feature when using the 'render-tui'\");\n\nuse crosstermion::crossterm::event::{KeyCode, KeyEventKind, KeyModifiers};\nuse crosstermion::{\n    input::{key_input_stream, Key},\n    terminal::{tui::new_terminal, AlternateRawScreen},\n};\n\n/// An event to be sent in the [`tui::render_with_input(…events)`](./fn.render_with_input.html) stream.\n///\n/// This way, the TUI can be instructed to draw frames or change the information to be displayed.\n#[derive(Debug, Clone)]\npub enum Event {\n    /// Draw a frame\n    Tick,\n    /// Send any key - can be used to simulate user input, and is typically generated by the TUI's own input loop.\n    Input(Key),\n    /// Change the size of the window to the given rectangle.\n    ///\n    /// Useful to embed the TUI into other terminal user interfaces that can resize dynamically.\n    SetWindowSize(Rect),\n    /// Set the title of the progress dashboard\n    SetTitle(String),\n    /// Provide a list of titles and lines to populate the side bar on the right.\n    SetInformation(Vec<Line>),\n    /// The way the GUI will respond to interrupt requests. See `Interrupt` for more information.\n    SetInterruptMode(Interrupt),\n}\n\n/// Returns a future that draws the terminal user interface indefinitely.\n///\n/// * `progress` is the progress tree whose information to visualize.\n///   It will usually be changing constantly while the TUI holds it.\n/// * `options` are configuring the TUI.\n/// * `events` is a stream of `Event`s which manipulate the TUI while it is running\n///\n/// Failure may occour if there is no terminal to draw into.\npub fn render_with_input(\n    out: impl std::io::Write,\n    progress: impl WeakRoot,\n    options: Options,\n    events: impl futures_core::Stream<Item = Event> + Send + Unpin,\n) -> Result<impl std::future::Future<Output = ()>, std::io::Error> {\n    let Options {\n        title,\n        frames_per_second,\n        window_size,\n        recompute_column_width_every_nth_frame,\n        throughput,\n        stop_if_progress_missing,\n    } = options;\n    let mut terminal = new_terminal(AlternateRawScreen::try_from(out)?)?;\n    terminal.hide_cursor()?;\n\n    let duration_per_frame = Duration::from_secs_f32(1.0 / frames_per_second);\n    let key_receive = key_input_stream();\n\n    let render_fut = async move {\n        let mut state = draw::State {\n            title,\n            duration_per_frame,\n            ..draw::State::default()\n        };\n        if throughput {\n            state.throughput = Some(Throughput::default());\n        }\n        let mut interrupt_mode = InterruptDrawInfo::Instantly;\n        let (entries_cap, messages_cap) = progress\n            .upgrade()\n            .map(|p| (p.num_tasks(), p.messages_capacity()))\n            .unwrap_or_default();\n        let mut entries = Vec::with_capacity(entries_cap);\n        let mut messages = Vec::with_capacity(messages_cap);\n        let mut events = ticker(duration_per_frame)\n            .map(|_| Event::Tick)\n            .or(key_receive.map(Event::Input))\n            .or(events);\n\n        let mut tick = 0usize;\n        let store_task_size_every = recompute_column_width_every_nth_frame.unwrap_or(1).max(1);\n        while let Some(event) = events.next().await {\n            let mut skip_redraw = false;\n            match event {\n                Event::Tick => {}\n                Event::Input(key) if key.kind != KeyEventKind::Release => match key.code {\n                    KeyCode::Char('c') | KeyCode::Char('[') if key.modifiers.contains(KeyModifiers::CONTROL) => {\n                        match interrupt_mode {\n                            InterruptDrawInfo::Instantly => break,\n                            InterruptDrawInfo::Deferred(_) => interrupt_mode = InterruptDrawInfo::Deferred(true),\n                        }\n                    }\n                    KeyCode::Esc | KeyCode::Char('q') => match interrupt_mode {\n                        InterruptDrawInfo::Instantly => break,\n                        InterruptDrawInfo::Deferred(_) => interrupt_mode = InterruptDrawInfo::Deferred(true),\n                    },\n                    KeyCode::Char('`') => state.hide_messages = !state.hide_messages,\n                    KeyCode::Char('~') => state.messages_fullscreen = !state.messages_fullscreen,\n                    KeyCode::Char('J') => state.message_offset = state.message_offset.saturating_add(1),\n                    KeyCode::Char('D') => state.message_offset = state.message_offset.saturating_add(10),\n                    KeyCode::Char('j') => state.task_offset = state.task_offset.saturating_add(1),\n                    KeyCode::Char('d') => state.task_offset = state.task_offset.saturating_add(10),\n                    KeyCode::Char('K') => state.message_offset = state.message_offset.saturating_sub(1),\n                    KeyCode::Char('U') => state.message_offset = state.message_offset.saturating_sub(10),\n                    KeyCode::Char('k') => state.task_offset = state.task_offset.saturating_sub(1),\n                    KeyCode::Char('u') => state.task_offset = state.task_offset.saturating_sub(10),\n                    KeyCode::Char('[') => state.hide_info = !state.hide_info,\n                    KeyCode::Char('{') => state.maximize_info = !state.maximize_info,\n                    _ => skip_redraw = true,\n                },\n                Event::Input(_) => skip_redraw = true,\n                Event::SetWindowSize(bound) => state.user_provided_window_size = Some(bound),\n                Event::SetTitle(title) => state.title = title,\n                Event::SetInformation(info) => state.information = info,\n                Event::SetInterruptMode(mode) => {\n                    interrupt_mode = match mode {\n                        Interrupt::Instantly => {\n                            if let InterruptDrawInfo::Deferred(true) = interrupt_mode {\n                                break;\n                            }\n                            InterruptDrawInfo::Instantly\n                        }\n                        Interrupt::Deferred => InterruptDrawInfo::Deferred(match interrupt_mode {\n                            InterruptDrawInfo::Deferred(interrupt_requested) => interrupt_requested,\n                            _ => false,\n                        }),\n                    };\n                }\n            }\n            if !skip_redraw {\n                tick += 1;\n\n                let progress = match progress.upgrade() {\n                    Some(progress) => progress,\n                    None if stop_if_progress_missing => break,\n                    None => continue,\n                };\n                progress.sorted_snapshot(&mut entries);\n                if stop_if_progress_missing && entries.is_empty() {\n                    break;\n                }\n                let terminal_window_size = terminal.pre_render().expect(\"pre-render to work\");\n                let window_size = state\n                    .user_provided_window_size\n                    .or(window_size)\n                    .unwrap_or(terminal_window_size);\n                let buf = terminal.current_buffer_mut();\n                if !state.hide_messages {\n                    progress.copy_messages(&mut messages);\n                }\n\n                draw::all(&mut state, interrupt_mode, &entries, &messages, window_size, buf);\n                if tick == 1 || tick % store_task_size_every == 0 || state.last_tree_column_width.unwrap_or(0) == 0 {\n                    state.next_tree_column_width = state.last_tree_column_width;\n                }\n                terminal.post_render().expect(\"post render to work\");\n            }\n        }\n        // Make sure the terminal responds right away when this future stops, to reset back to the 'non-alternate' buffer\n        drop(terminal);\n        io::stdout().flush().ok();\n    };\n    Ok(render_fut)\n}\n\n/// An easy-to-use version of `render_with_input(…)` that does not allow state manipulation via an event stream.\npub fn render(\n    out: impl std::io::Write,\n    progress: impl WeakRoot,\n    config: Options,\n) -> Result<impl std::future::Future<Output = ()>, std::io::Error> {\n    render_with_input(out, progress, config, futures_lite::stream::pending())\n}\n"
  },
  {
    "path": "src/render/tui/mod.rs",
    "content": "/*!\n* A module implementing a *terminal user interface* capable of visualizing all information stored in\n* [progress trees](../tree/struct.Root.html).\n*\n* **Please note** that it is behind the `render-tui` feature toggle, which is enabled by default.\n*\n* # Example\n*\n* ```should_panic\n* # fn main() -> Result<(), Box<dyn std::error::Error>> {\n* use futures::task::{LocalSpawnExt, SpawnExt};\n* use prodash::render::tui::ticker;\n* use prodash::Root;\n* // obtain a progress tree\n* let root = prodash::tree::Root::new();\n* // Configure the gui, provide it with a handle to the ever-changing tree\n* let render_fut = prodash::render::tui::render(\n*     std::io::stdout(),\n*     root.downgrade(),\n*     prodash::render::tui::Options {\n*         title: \"minimal example\".into(),\n*         ..Default::default()\n*     }\n* )?;\n* // As it runs forever, we want a way to stop it.\n* let (render_fut, abort_handle) = futures::future::abortable(render_fut);\n* let pool = futures::executor::LocalPool::new();\n* // Spawn the gui into the background…\n* let gui = pool.spawner().spawn_with_handle(async { render_fut.await.ok(); () })?;\n* // …and run tasks which provide progress\n* pool.spawner().spawn_local({\n*     use futures::StreamExt;\n*     let mut progress = root.add_child(\"task\");\n*     async move {\n*         progress.init(None, None);\n*         let mut count = 0;\n*         let  mut ticks = ticker(std::time::Duration::from_millis(100));\n*         while let Some(_) = ticks.next().await {\n*             progress.set(count);\n*             count += 1;\n*         }\n*     }\n* })?;\n* // …when we are done, tell the GUI to stop\n* abort_handle.abort();\n* //…and wait until it is done\n* futures::executor::block_on(gui);\n* # Ok(())\n* # }\n* ```\n*/\nmod draw;\nmod engine;\nmod utils;\n\npub use engine::*;\n/// Useful for bringing up the TUI without bringing in the `tui` crate yourself\npub use tui as tui_export;\npub use utils::ticker;\n"
  },
  {
    "path": "src/render/tui/utils.rs",
    "content": "use std::{future::Future, pin::Pin, task::Poll, time::Duration};\n\nuse async_io::Timer;\n\n/// Returns a stream of 'ticks', each being duration `dur` apart.\n///\n/// Can be useful to provide the TUI with additional events in regular intervals,\n/// when using the [`tui::render_with_input(…events)`](./fn.render_with_input.html) function.\npub fn ticker(dur: Duration) -> impl futures_core::Stream<Item = ()> {\n    let mut delay = Timer::after(dur);\n    futures_lite::stream::poll_fn(move |ctx| {\n        let res = Pin::new(&mut delay).poll(ctx);\n        match res {\n            Poll::Pending => Poll::Pending,\n            Poll::Ready(_) => {\n                delay = Timer::after(dur);\n                Poll::Ready(Some(()))\n            }\n        }\n    })\n}\n\npub const VERTICAL_LINE: &str = \"│\";\n\npub use tui_react::{draw_text_nowrap_fn, draw_text_with_ellipsis_nowrap, util::*};\n"
  },
  {
    "path": "src/throughput.rs",
    "content": "use std::{\n    collections::VecDeque,\n    sync::atomic::Ordering,\n    time::{Duration, SystemTime},\n};\n\nuse crate::{progress, unit};\n\nconst THROTTLE_INTERVAL: Duration = Duration::from_secs(1);\nconst ONCE_A_SECOND: Duration = Duration::from_secs(1);\n\n#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]\nstruct State {\n    observed: Duration,\n    last_value: progress::Step,\n    elapsed_values: VecDeque<(Duration, progress::Step)>,\n\n    last_update_duration: Duration,\n    precomputed_throughput: Option<progress::Step>,\n}\n\nimpl State {\n    fn new(value: progress::Step, elapsed: Duration) -> Self {\n        State {\n            observed: elapsed,\n            last_value: value,\n            elapsed_values: {\n                let mut v = VecDeque::with_capacity(6); // default frames per second\n                v.push_back((elapsed, value));\n                v\n            },\n\n            last_update_duration: elapsed,\n            precomputed_throughput: None,\n        }\n    }\n\n    fn compute_throughput(&mut self) -> progress::Step {\n        let mut observed: Duration = self.elapsed_values.iter().map(|e| e.0).sum();\n        while !self.elapsed_values.is_empty() && observed > ONCE_A_SECOND {\n            let candidate = self\n                .elapsed_values\n                .front()\n                .map(|e| e.0)\n                .expect(\"at least one item as we are in the checked loop\");\n            if observed.checked_sub(candidate).unwrap_or_default() <= ONCE_A_SECOND {\n                break;\n            }\n            observed -= candidate;\n            self.elapsed_values.pop_front();\n        }\n        let observed_value: progress::Step = self.elapsed_values.iter().map(|e| e.1).sum();\n        ((observed_value as f64 / observed.as_secs_f64()) * ONCE_A_SECOND.as_secs_f64()) as progress::Step\n    }\n\n    fn update(&mut self, value: progress::Step, elapsed: Duration) -> Option<unit::display::Throughput> {\n        self.observed += elapsed;\n        self.elapsed_values\n            .push_back((elapsed, value.saturating_sub(self.last_value)));\n        self.last_value = value;\n        if self.observed - self.last_update_duration > THROTTLE_INTERVAL {\n            self.precomputed_throughput = Some(self.compute_throughput());\n            self.last_update_duration = self.observed;\n        }\n        self.throughput()\n    }\n\n    fn throughput(&self) -> Option<unit::display::Throughput> {\n        self.precomputed_throughput.map(|tp| unit::display::Throughput {\n            value_change_in_timespan: tp,\n            timespan: ONCE_A_SECOND,\n        })\n    }\n}\n\n/// A utility to compute throughput of a set of progress values usually available to a renderer.\n#[derive(Default)]\npub struct Throughput {\n    sorted_by_key: Vec<(progress::Key, State)>,\n    updated_at: Option<SystemTime>,\n    elapsed: Option<Duration>,\n}\n\nimpl Throughput {\n    /// Called at the beginning of the drawing of a renderer to remember at which time progress values are\n    /// going to be updated with [`update_and_get(…)`][Throughput::update_and_get()].\n    pub fn update_elapsed(&mut self) {\n        let now = SystemTime::now();\n        self.elapsed = self.updated_at.and_then(|then| now.duration_since(then).ok());\n        self.updated_at = Some(now);\n    }\n\n    /// Lookup or create the progress value at `key` and set its current `progress`, returning its computed\n    /// throughput.\n    pub fn update_and_get(\n        &mut self,\n        key: &progress::Key,\n        progress: Option<&progress::Value>,\n    ) -> Option<unit::display::Throughput> {\n        progress.and_then(|progress| {\n            self.elapsed\n                .and_then(|elapsed| match self.sorted_by_key.binary_search_by_key(key, |t| t.0) {\n                    Ok(index) => self.sorted_by_key[index]\n                        .1\n                        .update(progress.step.load(Ordering::SeqCst), elapsed),\n                    Err(index) => {\n                        let state = State::new(progress.step.load(Ordering::SeqCst), elapsed);\n                        let tp = state.throughput();\n                        self.sorted_by_key.insert(index, (*key, state));\n                        tp\n                    }\n                })\n        })\n    }\n\n    /// Compare the keys in `sorted_values` with our internal state and remove all missing tasks from it.\n    ///\n    /// This should be called after [`update_and_get(…)`][Throughput::update_and_get()] to pick up removed/finished\n    /// progress.\n    pub fn reconcile(&mut self, sorted_values: &[(progress::Key, progress::Task)]) {\n        self.sorted_by_key\n            .retain(|(key, _)| sorted_values.binary_search_by_key(key, |e| e.0).is_ok());\n    }\n}\n"
  },
  {
    "path": "src/time.rs",
    "content": "#[cfg(feature = \"local-time\")]\nmod localtime {\n    use std::time::SystemTime;\n\n    use jiff::Zoned;\n\n    /// Return a string representing the current date and time as localtime.\n    ///\n    /// Available with the `localtime` feature toggle.\n    pub fn format_now_datetime_seconds() -> String {\n        Zoned::now().strftime(\"%F %T %Z\").to_string()\n    }\n\n    /// Return a string representing the current time as localtime.\n    ///\n    /// Available with the `localtime` feature toggle.\n    pub fn format_time_for_messages(time: SystemTime) -> String {\n        Zoned::try_from(time)\n            .expect(\"system time is always in range -9999-01-01..=9999-12-31\")\n            .strftime(\"%T\")\n            .to_string()\n    }\n}\n\n/// An `hours:minute:seconds` format.\npub const DATE_TIME_HMS: usize = \"00:51:45\".len();\n\n#[cfg(not(feature = \"local-time\"))]\nmod utc {\n    use std::time::SystemTime;\n\n    use super::DATE_TIME_HMS;\n\n    /// Return a string representing the current date and time as UTC.\n    ///\n    /// Available without the `localtime` feature toggle.\n    pub fn format_time_for_messages(time: SystemTime) -> String {\n        let time = jiff::Timestamp::try_from(time).expect(\"reasonable system time\");\n        time.strftime(\"%T\").to_string()\n    }\n\n    /// Return a string representing the current time as UTC.\n    ///\n    /// Available without the `localtime` feature toggle.\n    pub fn format_now_datetime_seconds() -> String {\n        jiff::Timestamp::now().strftime(\"%FT%T\").to_string()\n    }\n}\n\n#[cfg(feature = \"local-time\")]\npub use localtime::*;\n#[cfg(not(feature = \"local-time\"))]\npub use utc::*;\n"
  },
  {
    "path": "src/traits.rs",
    "content": "use std::time::Instant;\n\nuse crate::{messages::MessageLevel, progress, progress::Id, Unit};\n\n/// A trait for describing hierarchical progress.\npub trait NestedProgress: Progress {\n    /// The type of progress returned by [`add_child()`][Progress::add_child()].\n    type SubProgress: NestedProgress;\n\n    /// Adds a new child, whose parent is this instance, with the given `name`.\n    ///\n    /// This will make the child progress to appear contained in the parent progress.\n    /// Note that such progress does not have a stable identifier, which can be added\n    /// with [`add_child_with_id()`][Progress::add_child_with_id()] if desired.\n    fn add_child(&mut self, name: impl Into<String>) -> Self::SubProgress;\n\n    /// Adds a new child, whose parent is this instance, with the given `name` and `id`.\n    ///\n    /// This will make the child progress to appear contained in the parent progress, and it can be identified\n    /// using `id`.\n    fn add_child_with_id(&mut self, name: impl Into<String>, id: Id) -> Self::SubProgress;\n}\n\n/// A thread-safe read-only counter, with unknown limits.\npub trait Count {\n    /// Set the current progress to the given `step`. The cost of this call is negligible,\n    /// making manual throttling *not* necessary.\n    ///\n    /// **Note**: that this call has no effect unless `init(…)` was called before.\n    fn set(&self, step: progress::Step);\n\n    /// Returns the current step, as controlled by `inc*(…)` calls\n    fn step(&self) -> progress::Step;\n\n    /// Increment the current progress to the given `step`.\n    /// The cost of this call is negligible, making manual throttling *not* necessary.\n    fn inc_by(&self, step: progress::Step);\n\n    /// Increment the current progress to the given 1. The cost of this call is negligible,\n    /// making manual throttling *not* necessary.\n    fn inc(&self) {\n        self.inc_by(1)\n    }\n\n    /// Return an atomic counter for direct access to the underlying state.\n    ///\n    /// This is useful if multiple threads want to access the same progress, without the need\n    /// for provide each their own progress and aggregating the result.\n    fn counter(&self) -> StepShared;\n}\n\n/// An object-safe trait for describing hierarchical progress.\n///\n/// This will be automatically implemented for any type that implements\n/// [`NestedProgress`].\npub trait DynNestedProgress: Progress + impls::Sealed {\n    /// See [`NestedProgress::add_child`]\n    fn add_child(&mut self, name: String) -> BoxedDynNestedProgress;\n\n    /// See [`NestedProgress::add_child_with_id`]\n    fn add_child_with_id(&mut self, name: String, id: Id) -> BoxedDynNestedProgress;\n}\n\n/// An opaque type for storing [`DynNestedProgress`].\npub struct BoxedDynNestedProgress(Box<dyn DynNestedProgress>);\n\n/// An owned version of [`Progress`] which can itself implement said trait.\npub type BoxedProgress = Box<dyn Progress>;\n\n/// A bridge type that implements [`NestedProgress`] for any type that implements [`DynNestedProgress`].\npub struct DynNestedProgressToNestedProgress<T: ?Sized>(pub T);\n\n/// A trait for describing non-hierarchical progress.\n///\n/// It differs by not being able to add child progress dynamically, but in turn is object safe. It's recommended to\n/// use this trait whenever there is no need to add child progress, at the leaf of a computation.\n// NOTE: keep this in-sync with `Progress`.\npub trait Progress: Count + Send + Sync {\n    /// Initialize the Item for receiving progress information.\n    ///\n    /// If `max` is `Some(…)`, it will be treated as upper bound. When progress is [set(…)](./struct.Item.html#method.set)\n    /// it should not exceed the given maximum.\n    /// If `max` is `None`, the progress is unbounded. Use this if the amount of work cannot accurately\n    /// be determined in advance.\n    ///\n    /// If `unit` is `Some(…)`, it is used for display purposes only. See `prodash::Unit` for more information.\n    ///\n    /// If both `unit` and `max` are `None`, the item will be reset to be equivalent to 'uninitialized'.\n    ///\n    /// If this method is never called, this `Progress` instance will serve as organizational unit, useful to add more structure\n    /// to the progress tree (e.g. a headline).\n    ///\n    /// **Note** that this method can be called multiple times, changing the bounded-ness and unit at will.\n    fn init(&mut self, max: Option<progress::Step>, unit: Option<Unit>);\n\n    /// Returns the (cloned) unit associated with this Progress\n    fn unit(&self) -> Option<Unit> {\n        None\n    }\n\n    /// Returns the maximum about of items we expect, as provided with the `init(…)` call\n    fn max(&self) -> Option<progress::Step> {\n        None\n    }\n\n    /// Set the maximum value to `max` and return the old maximum value.\n    fn set_max(&mut self, _max: Option<progress::Step>) -> Option<progress::Step> {\n        None\n    }\n\n    /// Set the name of the instance, altering the value given when crating it with `add_child(…)`\n    /// The progress is allowed to discard it.\n    fn set_name(&mut self, name: String);\n\n    /// Get the name of the instance as given when creating it with `add_child(…)`\n    /// The progress is allowed to not be named, thus there is no guarantee that a previously set names 'sticks'.\n    fn name(&self) -> Option<String>;\n\n    /// Get a stable identifier for the progress instance.\n    /// Note that it could be [unknown][crate::progress::UNKNOWN].\n    fn id(&self) -> Id;\n\n    /// Create a `message` of the given `level` and store it with the progress tree.\n    ///\n    /// Use this to provide additional,human-readable information about the progress\n    /// made, including indicating success or failure.\n    fn message(&self, level: MessageLevel, message: String);\n\n    /// Create a message providing additional information about the progress thus far.\n    fn info(&self, message: String) {\n        self.message(MessageLevel::Info, message)\n    }\n    /// Create a message indicating the task is done successfully\n    fn done(&self, message: String) {\n        self.message(MessageLevel::Success, message)\n    }\n    /// Create a message indicating the task failed\n    fn fail(&self, message: String) {\n        self.message(MessageLevel::Failure, message)\n    }\n    /// A shorthand to print throughput information\n    fn show_throughput(&self, start: Instant) {\n        let step = self.step();\n        match self.unit() {\n            Some(unit) => self.show_throughput_with(start, step, unit, MessageLevel::Info),\n            None => {\n                let elapsed = start.elapsed().as_secs_f32();\n                let steps_per_second = (step as f32 / elapsed) as progress::Step;\n                self.info(format!(\n                    \"done {} items in {:.02}s ({} items/s)\",\n                    step, elapsed, steps_per_second\n                ))\n            }\n        };\n    }\n\n    /// A shorthand to print throughput information, with the given step and unit, and message level.\n    fn show_throughput_with(&self, start: Instant, step: progress::Step, unit: Unit, level: MessageLevel) {\n        use std::fmt::Write;\n        let elapsed = start.elapsed().as_secs_f32();\n        let steps_per_second = (step as f32 / elapsed) as progress::Step;\n        let mut buf = String::with_capacity(128);\n        let unit = unit.as_display_value();\n        let push_unit = |buf: &mut String| {\n            buf.push(' ');\n            let len_before_unit = buf.len();\n            unit.display_unit(buf, step).ok();\n            if buf.len() == len_before_unit {\n                buf.pop();\n            }\n        };\n\n        buf.push_str(\"done \");\n        unit.display_current_value(&mut buf, step, None).ok();\n        push_unit(&mut buf);\n\n        buf.write_fmt(format_args!(\" in {:.02}s (\", elapsed)).ok();\n        unit.display_current_value(&mut buf, steps_per_second, None).ok();\n        push_unit(&mut buf);\n        buf.push_str(\"/s)\");\n\n        self.message(level, buf);\n    }\n}\n\nuse crate::{\n    messages::{Message, MessageCopyState},\n    progress::StepShared,\n};\n\n/// The top-level root as weak handle, which needs an upgrade to become a usable root.\n///\n/// If the underlying reference isn't present anymore, such upgrade will fail permanently.\npub trait WeakRoot {\n    /// The type implementing the `Root` trait\n    type Root: Root;\n\n    /// Equivalent to `std::sync::Weak::upgrade()`.\n    fn upgrade(&self) -> Option<Self::Root>;\n}\n\n/// The top level of a progress task hierarchy, with `progress::Task`s identified with `progress::Key`s\npub trait Root {\n    /// The type implementing the `WeakRoot` trait\n    type WeakRoot: WeakRoot;\n\n    /// Returns the maximum amount of messages we can keep before overwriting older ones.\n    fn messages_capacity(&self) -> usize;\n\n    /// Returns the current amount of tasks underneath the root, transitively.\n    /// **Note** that this is at most a guess as tasks can be added and removed in parallel.\n    fn num_tasks(&self) -> usize;\n\n    /// Copy the entire progress tree into the given `out` vector, so that\n    /// it can be traversed from beginning to end in order of hierarchy.\n    /// The `out` vec will be cleared automatically.\n    fn sorted_snapshot(&self, out: &mut Vec<(progress::Key, progress::Task)>);\n\n    /// Copy all messages from the internal ring buffer into the given `out`\n    /// vector. Messages are ordered from oldest to newest.\n    fn copy_messages(&self, out: &mut Vec<Message>);\n\n    /// Copy only new messages from the internal ring buffer into the given `out`\n    /// vector. Messages are ordered from oldest to newest.\n    fn copy_new_messages(&self, out: &mut Vec<Message>, prev: Option<MessageCopyState>) -> MessageCopyState;\n\n    /// Similar to `Arc::downgrade()`\n    fn downgrade(&self) -> Self::WeakRoot;\n}\n\nmod impls {\n    use std::{\n        ops::{Deref, DerefMut},\n        time::Instant,\n    };\n\n    use crate::traits::{BoxedProgress, Progress};\n    use crate::{\n        messages::MessageLevel,\n        progress::{Id, Step, StepShared},\n        BoxedDynNestedProgress, Count, DynNestedProgress, DynNestedProgressToNestedProgress, NestedProgress, Unit,\n    };\n\n    pub trait Sealed {}\n\n    impl<T> Count for &T\n    where\n        T: Count + ?Sized,\n    {\n        fn set(&self, step: Step) {\n            (*self).set(step)\n        }\n\n        fn step(&self) -> Step {\n            (*self).step()\n        }\n\n        fn inc_by(&self, step: Step) {\n            (*self).inc_by(step)\n        }\n\n        fn inc(&self) {\n            (*self).inc()\n        }\n\n        fn counter(&self) -> StepShared {\n            (*self).counter()\n        }\n    }\n\n    impl<T> Count for &mut T\n    where\n        T: Count + ?Sized,\n    {\n        fn set(&self, step: Step) {\n            self.deref().set(step)\n        }\n\n        fn step(&self) -> Step {\n            self.deref().step()\n        }\n\n        fn inc_by(&self, step: Step) {\n            self.deref().inc_by(step)\n        }\n\n        fn inc(&self) {\n            self.deref().inc()\n        }\n\n        fn counter(&self) -> StepShared {\n            self.deref().counter()\n        }\n    }\n\n    impl<T> Progress for &mut T\n    where\n        T: Progress + ?Sized,\n    {\n        fn init(&mut self, max: Option<Step>, unit: Option<Unit>) {\n            self.deref_mut().init(max, unit)\n        }\n\n        fn unit(&self) -> Option<Unit> {\n            self.deref().unit()\n        }\n\n        fn max(&self) -> Option<Step> {\n            self.deref().max()\n        }\n\n        fn set_max(&mut self, max: Option<Step>) -> Option<Step> {\n            self.deref_mut().set_max(max)\n        }\n\n        fn set_name(&mut self, name: String) {\n            self.deref_mut().set_name(name)\n        }\n\n        fn name(&self) -> Option<String> {\n            self.deref().name()\n        }\n\n        fn id(&self) -> Id {\n            self.deref().id()\n        }\n\n        fn message(&self, level: MessageLevel, message: String) {\n            self.deref().message(level, message)\n        }\n\n        fn info(&self, message: String) {\n            self.deref().info(message)\n        }\n\n        fn done(&self, message: String) {\n            self.deref().done(message)\n        }\n\n        fn fail(&self, message: String) {\n            self.deref().fail(message)\n        }\n\n        fn show_throughput(&self, start: Instant) {\n            self.deref().show_throughput(start)\n        }\n\n        fn show_throughput_with(&self, start: Instant, step: Step, unit: Unit, level: MessageLevel) {\n            self.deref().show_throughput_with(start, step, unit, level)\n        }\n    }\n\n    impl<T> NestedProgress for &mut T\n    where\n        T: NestedProgress + ?Sized,\n    {\n        type SubProgress = T::SubProgress;\n\n        fn add_child(&mut self, name: impl Into<String>) -> Self::SubProgress {\n            self.deref_mut().add_child(name)\n        }\n\n        fn add_child_with_id(&mut self, name: impl Into<String>, id: Id) -> Self::SubProgress {\n            self.deref_mut().add_child_with_id(name, id)\n        }\n    }\n\n    impl<T> Sealed for T where T: NestedProgress + ?Sized {}\n\n    impl<T, SubP> DynNestedProgress for T\n    where\n        T: NestedProgress<SubProgress = SubP> + ?Sized,\n        SubP: NestedProgress + 'static,\n    {\n        fn add_child(&mut self, name: String) -> BoxedDynNestedProgress {\n            BoxedDynNestedProgress::new(self.add_child(name))\n        }\n\n        fn add_child_with_id(&mut self, name: String, id: Id) -> BoxedDynNestedProgress {\n            BoxedDynNestedProgress::new(self.add_child_with_id(name, id))\n        }\n    }\n\n    impl BoxedDynNestedProgress {\n        /// Create new instance from a `DynProgress` implementation.\n        pub fn new(progress: impl DynNestedProgress + 'static) -> Self {\n            Self(Box::new(progress))\n        }\n    }\n\n    impl Progress for BoxedDynNestedProgress {\n        fn init(&mut self, max: Option<Step>, unit: Option<Unit>) {\n            self.0.init(max, unit)\n        }\n\n        fn unit(&self) -> Option<Unit> {\n            self.0.unit()\n        }\n\n        fn max(&self) -> Option<Step> {\n            self.0.max()\n        }\n\n        fn set_max(&mut self, max: Option<Step>) -> Option<Step> {\n            self.0.set_max(max)\n        }\n\n        fn set_name(&mut self, name: String) {\n            self.0.set_name(name)\n        }\n\n        fn name(&self) -> Option<String> {\n            self.0.name()\n        }\n\n        fn id(&self) -> Id {\n            self.0.id()\n        }\n\n        fn message(&self, level: MessageLevel, message: String) {\n            self.0.message(level, message)\n        }\n\n        fn show_throughput(&self, start: Instant) {\n            self.0.show_throughput(start)\n        }\n\n        fn show_throughput_with(&self, start: Instant, step: Step, unit: Unit, level: MessageLevel) {\n            self.0.show_throughput_with(start, step, unit, level)\n        }\n    }\n\n    impl Progress for BoxedProgress {\n        fn init(&mut self, max: Option<Step>, unit: Option<Unit>) {\n            self.deref_mut().init(max, unit)\n        }\n\n        fn unit(&self) -> Option<Unit> {\n            self.deref().unit()\n        }\n\n        fn max(&self) -> Option<Step> {\n            self.deref().max()\n        }\n\n        fn set_max(&mut self, max: Option<Step>) -> Option<Step> {\n            self.deref_mut().set_max(max)\n        }\n\n        fn set_name(&mut self, name: String) {\n            self.deref_mut().set_name(name)\n        }\n\n        fn name(&self) -> Option<String> {\n            self.deref().name()\n        }\n\n        fn id(&self) -> Id {\n            self.deref().id()\n        }\n\n        fn message(&self, level: MessageLevel, message: String) {\n            self.deref().message(level, message)\n        }\n\n        fn show_throughput(&self, start: Instant) {\n            self.deref().show_throughput(start)\n        }\n\n        fn show_throughput_with(&self, start: Instant, step: Step, unit: Unit, level: MessageLevel) {\n            self.deref().show_throughput_with(start, step, unit, level)\n        }\n    }\n\n    impl Count for BoxedDynNestedProgress {\n        fn set(&self, step: Step) {\n            self.0.set(step)\n        }\n\n        fn step(&self) -> Step {\n            self.0.step()\n        }\n\n        fn inc_by(&self, step: Step) {\n            self.0.inc_by(step)\n        }\n\n        fn inc(&self) {\n            self.0.inc()\n        }\n\n        fn counter(&self) -> StepShared {\n            self.0.counter()\n        }\n    }\n\n    impl Count for BoxedProgress {\n        fn set(&self, step: Step) {\n            self.deref().set(step)\n        }\n\n        fn step(&self) -> Step {\n            self.deref().step()\n        }\n\n        fn inc_by(&self, step: Step) {\n            self.deref().inc_by(step)\n        }\n\n        fn inc(&self) {\n            self.deref().inc()\n        }\n\n        fn counter(&self) -> StepShared {\n            self.deref().counter()\n        }\n    }\n\n    impl NestedProgress for BoxedDynNestedProgress {\n        type SubProgress = Self;\n\n        fn add_child(&mut self, name: impl Into<String>) -> Self::SubProgress {\n            self.0.add_child(name.into())\n        }\n\n        fn add_child_with_id(&mut self, name: impl Into<String>, id: Id) -> Self::SubProgress {\n            self.0.add_child_with_id(name.into(), id)\n        }\n    }\n\n    impl<T> Progress for DynNestedProgressToNestedProgress<T>\n    where\n        T: ?Sized + Progress,\n    {\n        fn init(&mut self, max: Option<Step>, unit: Option<Unit>) {\n            self.0.init(max, unit)\n        }\n\n        fn unit(&self) -> Option<Unit> {\n            self.0.unit()\n        }\n\n        fn max(&self) -> Option<Step> {\n            self.0.max()\n        }\n\n        fn set_max(&mut self, max: Option<Step>) -> Option<Step> {\n            self.0.set_max(max)\n        }\n\n        fn set_name(&mut self, name: String) {\n            self.0.set_name(name)\n        }\n\n        fn name(&self) -> Option<String> {\n            self.0.name()\n        }\n\n        fn id(&self) -> Id {\n            self.0.id()\n        }\n\n        fn message(&self, level: MessageLevel, message: String) {\n            self.0.message(level, message)\n        }\n\n        fn show_throughput(&self, start: Instant) {\n            self.0.show_throughput(start)\n        }\n\n        fn show_throughput_with(&self, start: Instant, step: Step, unit: Unit, level: MessageLevel) {\n            self.0.show_throughput_with(start, step, unit, level)\n        }\n    }\n\n    impl<T> Count for DynNestedProgressToNestedProgress<T>\n    where\n        T: ?Sized + Count,\n    {\n        fn set(&self, step: Step) {\n            self.0.set(step)\n        }\n\n        fn step(&self) -> Step {\n            self.0.step()\n        }\n\n        fn inc_by(&self, step: Step) {\n            self.0.inc_by(step)\n        }\n\n        fn inc(&self) {\n            self.0.inc()\n        }\n\n        fn counter(&self) -> StepShared {\n            self.0.counter()\n        }\n    }\n\n    impl<T> NestedProgress for DynNestedProgressToNestedProgress<T>\n    where\n        T: DynNestedProgress + ?Sized,\n    {\n        type SubProgress = BoxedDynNestedProgress;\n\n        fn add_child(&mut self, name: impl Into<String>) -> Self::SubProgress {\n            self.0.add_child(name.into())\n        }\n\n        fn add_child_with_id(&mut self, name: impl Into<String>, id: Id) -> Self::SubProgress {\n            self.0.add_child_with_id(name.into(), id)\n        }\n    }\n}\n"
  },
  {
    "path": "src/tree/item.rs",
    "content": "use std::{\n    fmt::Debug,\n    ops::Deref,\n    sync::{\n        atomic::{AtomicUsize, Ordering},\n        Arc,\n    },\n    time::SystemTime,\n};\n\nuse parking_lot::Mutex;\n\nuse crate::{\n    messages::MessageLevel,\n    progress::{Id, State, Step, StepShared, Task, Value},\n    tree::Item,\n    unit::Unit,\n};\n\nimpl Drop for Item {\n    fn drop(&mut self) {\n        self.tree.remove(&self.key);\n    }\n}\n\nimpl Debug for Item {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"Item\")\n            .field(\"key\", &self.key)\n            .field(\"value\", &self.value)\n            .finish_non_exhaustive()\n    }\n}\n\nimpl Item {\n    /// Initialize the Item for receiving progress information.\n    ///\n    /// If `max` is `Some(…)`, it will be treated as upper bound. When progress is [set(…)](./struct.Item.html#method.set)\n    /// it should not exceed the given maximum.\n    /// If `max` is `None`, the progress is unbounded. Use this if the amount of work cannot accurately\n    /// be determined.\n    ///\n    /// If `unit` is `Some(…)`, it is used for display purposes only. It should be using the plural.\n    ///\n    /// If this method is never called, this `Item` will serve as organizational unit, useful to add more structure\n    /// to the progress tree.\n    ///\n    /// **Note** that this method can be called multiple times, changing the bounded-ness and unit at will.\n    pub fn init(&self, max: Option<usize>, unit: Option<Unit>) {\n        #[cfg(feature = \"progress-tree-hp-hashmap\")]\n        {\n            if let Some(mut r) = self.tree.get_mut(&self.key) {\n                self.value.store(0, Ordering::SeqCst);\n                r.value_mut().progress = (max.is_some() || unit.is_some()).then(|| Value {\n                    done_at: max,\n                    unit,\n                    step: Arc::clone(&self.value),\n                    ..Default::default()\n                })\n            };\n        }\n        #[cfg(not(feature = \"progress-tree-hp-hashmap\"))]\n        {\n            self.tree.get_mut(&self.key, |v| {\n                self.value.store(0, Ordering::SeqCst);\n                v.progress = (max.is_some() || unit.is_some()).then(|| Value {\n                    done_at: max,\n                    unit,\n                    step: Arc::clone(&self.value),\n                    ..Default::default()\n                });\n            });\n        }\n    }\n\n    fn alter_progress(&self, f: impl FnMut(&mut Value)) {\n        #[cfg(feature = \"progress-tree-hp-hashmap\")]\n        {\n            if let Some(mut r) = self.tree.get_mut(&self.key) {\n                // NOTE: since we wrap around, if there are more tasks than we can have IDs for,\n                // and if all these tasks are still alive, two progress trees may see the same ID\n                // when these go out of scope, they delete the key and the other tree will not find\n                // its value anymore. Besides, it's probably weird to see tasks changing their progress\n                // all the time…\n                r.value_mut().progress.as_mut().map(f);\n            };\n        }\n        #[cfg(not(feature = \"progress-tree-hp-hashmap\"))]\n        {\n            self.tree.get_mut(&self.key, |v| {\n                v.progress.as_mut().map(f);\n            });\n        }\n    }\n\n    /// Set the name of this task's progress to the given `name`.\n    pub fn set_name(&self, name: impl Into<String>) {\n        #[cfg(feature = \"progress-tree-hp-hashmap\")]\n        {\n            if let Some(mut r) = self.tree.get_mut(&self.key) {\n                r.value_mut().name = name.into();\n            };\n        }\n        #[cfg(not(feature = \"progress-tree-hp-hashmap\"))]\n        {\n            self.tree.get_mut(&self.key, |v| {\n                v.name = name.into();\n            });\n        }\n    }\n\n    /// Get the name of this task's progress\n    pub fn name(&self) -> Option<String> {\n        #[cfg(feature = \"progress-tree-hp-hashmap\")]\n        {\n            self.tree.get(&self.key).map(|r| r.value().name.to_owned())\n        }\n        #[cfg(not(feature = \"progress-tree-hp-hashmap\"))]\n        {\n            self.tree.get(&self.key, |v| v.name.to_owned())\n        }\n    }\n\n    /// Get the stable identifier of this instance.\n    pub fn id(&self) -> Id {\n        #[cfg(feature = \"progress-tree-hp-hashmap\")]\n        {\n            self.tree\n                .get(&self.key)\n                .map(|r| r.value().id)\n                .unwrap_or(crate::progress::UNKNOWN)\n        }\n        #[cfg(not(feature = \"progress-tree-hp-hashmap\"))]\n        {\n            self.tree.get(&self.key, |v| v.id).unwrap_or(crate::progress::UNKNOWN)\n        }\n    }\n\n    /// Returns the current step, as controlled by `inc*(…)` calls\n    pub fn step(&self) -> Option<Step> {\n        self.value.load(Ordering::Relaxed).into()\n    }\n\n    /// Returns the maximum about of items we expect, as provided with the `init(…)` call\n    pub fn max(&self) -> Option<Step> {\n        #[cfg(feature = \"progress-tree-hp-hashmap\")]\n        {\n            self.tree\n                .get(&self.key)\n                .and_then(|r| r.value().progress.as_ref().and_then(|p| p.done_at))\n        }\n        #[cfg(not(feature = \"progress-tree-hp-hashmap\"))]\n        {\n            self.tree\n                .get(&self.key, |v| v.progress.as_ref().and_then(|p| p.done_at))\n                .flatten()\n        }\n    }\n\n    /// Set the maximum value to `max` and return the old maximum value.\n    pub fn set_max(&self, max: Option<Step>) -> Option<Step> {\n        #[cfg(feature = \"progress-tree-hp-hashmap\")]\n        {\n            self.tree\n                .get_mut(&self.key)?\n                .value_mut()\n                .progress\n                .as_mut()\n                .and_then(|p| {\n                    let prev = p.done_at;\n                    p.done_at = max;\n                    prev\n                })\n        }\n        #[cfg(not(feature = \"progress-tree-hp-hashmap\"))]\n        {\n            self.tree\n                .get_mut(&self.key, |v| {\n                    v.progress.as_mut().and_then(|p| {\n                        let prev = p.done_at;\n                        p.done_at = max;\n                        prev\n                    })\n                })\n                .flatten()\n        }\n    }\n\n    /// Returns the (cloned) unit associated with this Progress\n    pub fn unit(&self) -> Option<Unit> {\n        #[cfg(feature = \"progress-tree-hp-hashmap\")]\n        {\n            self.tree\n                .get(&self.key)\n                .and_then(|r| r.value().progress.as_ref().and_then(|p| p.unit.clone()))\n        }\n        #[cfg(not(feature = \"progress-tree-hp-hashmap\"))]\n        {\n            self.tree\n                .get(&self.key, |v| v.progress.as_ref().and_then(|p| p.unit.clone()))\n                .flatten()\n        }\n    }\n\n    /// Set the current progress to the given `step`.\n    ///\n    /// **Note**: that this call has no effect unless `init(…)` was called before.\n    pub fn set(&self, step: Step) {\n        self.value.store(step, Ordering::SeqCst);\n    }\n\n    /// Increment the current progress by the given `step`.\n    ///\n    /// **Note**: that this call has no effect unless `init(…)` was called before.\n    pub fn inc_by(&self, step: Step) {\n        self.value.fetch_add(step, Ordering::Relaxed);\n    }\n\n    /// Increment the current progress by one.\n    ///\n    /// **Note**: that this call has no effect unless `init(…)` was called before.\n    pub fn inc(&self) {\n        self.value.fetch_add(1, Ordering::Relaxed);\n    }\n\n    /// Call to indicate that progress cannot be indicated, and that the task cannot be interrupted.\n    /// Use this, as opposed to `halted(…)`, if a non-interruptable call is about to be made without support\n    /// for any progress indication.\n    ///\n    /// If `eta` is `Some(…)`, it specifies the time at which this task is expected to\n    /// make progress again.\n    ///\n    /// The halted-state is undone next time [`tree::Item::running(…)`][Item::running()] is called.\n    pub fn blocked(&self, reason: &'static str, eta: Option<SystemTime>) {\n        self.alter_progress(|p| p.state = State::Blocked(reason, eta));\n    }\n\n    /// Call to indicate that progress cannot be indicated, even though the task can be interrupted.\n    /// Use this, as opposed to `blocked(…)`, if an interruptable call is about to be made without support\n    /// for any progress indication.\n    ///\n    /// If `eta` is `Some(…)`, it specifies the time at which this task is expected to\n    /// make progress again.\n    ///\n    /// The halted-state is undone next time [`tree::Item::running(…)`][Item::running()] is called.\n    pub fn halted(&self, reason: &'static str, eta: Option<SystemTime>) {\n        self.alter_progress(|p| p.state = State::Halted(reason, eta));\n    }\n\n    /// Call to indicate that progress is back in running state, which should be called after the reason for\n    /// calling `blocked()` or `halted()` has passed.\n    pub fn running(&self) {\n        self.alter_progress(|p| p.state = State::Running);\n    }\n\n    /// Adds a new child `Tree`, whose parent is this instance, with the given `name`.\n    ///\n    /// **Important**: The depth of the hierarchy is limited to [`tree::Key::max_level`](./struct.Key.html#method.max_level).\n    /// Exceeding the level will be ignored, and new tasks will be added to this instance's\n    /// level instead.\n    pub fn add_child(&mut self, name: impl Into<String>) -> Item {\n        self.add_child_with_id(name, crate::progress::UNKNOWN)\n    }\n\n    /// Adds a new child `Tree`, whose parent is this instance, with the given `name` and `id`.\n    ///\n    /// **Important**: The depth of the hierarchy is limited to [`tree::Key::max_level`](./struct.Key.html#method.max_level).\n    /// Exceeding the level will be ignored, and new tasks will be added to this instance's\n    /// level instead.\n    pub fn add_child_with_id(&mut self, name: impl Into<String>, id: Id) -> Item {\n        let child_key = self.key.add_child(self.highest_child_id);\n        let task = Task {\n            name: name.into(),\n            id,\n            progress: None,\n        };\n        #[cfg(feature = \"progress-tree-hp-hashmap\")]\n        self.tree.insert(child_key, task);\n        #[cfg(not(feature = \"progress-tree-hp-hashmap\"))]\n        self.tree.insert(child_key, task);\n        self.highest_child_id = self.highest_child_id.wrapping_add(1);\n        Item {\n            highest_child_id: 0,\n            value: Default::default(),\n            key: child_key,\n            tree: Arc::clone(&self.tree),\n            messages: Arc::clone(&self.messages),\n        }\n    }\n\n    /// Create a `message` of the given `level` and store it with the progress tree.\n    ///\n    /// Use this to provide additional,human-readable information about the progress\n    /// made, including indicating success or failure.\n    pub fn message(&self, level: MessageLevel, message: impl Into<String>) {\n        let message: String = message.into();\n        self.messages.lock().push_overwrite(\n            level,\n            {\n                let name;\n                #[cfg(feature = \"progress-tree-hp-hashmap\")]\n                {\n                    name = self.tree.get(&self.key).map(|v| v.name.to_owned()).unwrap_or_default();\n                }\n                #[cfg(not(feature = \"progress-tree-hp-hashmap\"))]\n                {\n                    name = self.tree.get(&self.key, |v| v.name.to_owned()).unwrap_or_default()\n                }\n\n                #[cfg(feature = \"progress-tree-log\")]\n                match level {\n                    MessageLevel::Failure => crate::warn!(\"{} → {}\", name, message),\n                    MessageLevel::Info | MessageLevel::Success => crate::info!(\"{} → {}\", name, message),\n                };\n\n                name\n            },\n            message,\n        )\n    }\n\n    /// Create a message indicating the task is done\n    pub fn done(&mut self, message: impl Into<String>) {\n        self.message(MessageLevel::Success, message)\n    }\n\n    /// Create a message indicating the task failed\n    pub fn fail(&mut self, message: impl Into<String>) {\n        self.message(MessageLevel::Failure, message)\n    }\n\n    /// Create a message providing additional information about the progress thus far.\n    pub fn info(&mut self, message: impl Into<String>) {\n        self.message(MessageLevel::Info, message)\n    }\n\n    pub(crate) fn deep_clone(&self) -> Item {\n        Item {\n            key: self.key,\n            value: Arc::new(AtomicUsize::new(self.value.load(Ordering::SeqCst))),\n            highest_child_id: self.highest_child_id,\n            tree: Arc::new(self.tree.deref().clone()),\n            messages: Arc::new(Mutex::new(self.messages.lock().clone())),\n        }\n    }\n}\n\nimpl crate::Count for Item {\n    fn set(&self, step: usize) {\n        Item::set(self, step)\n    }\n\n    fn step(&self) -> usize {\n        Item::step(self).unwrap_or(0)\n    }\n\n    fn inc_by(&self, step: usize) {\n        self.inc_by(step)\n    }\n\n    fn counter(&self) -> StepShared {\n        Arc::clone(&self.value)\n    }\n}\n\nimpl crate::Progress for Item {\n    fn init(&mut self, max: Option<Step>, unit: Option<Unit>) {\n        Item::init(self, max, unit)\n    }\n\n    fn unit(&self) -> Option<Unit> {\n        Item::unit(self)\n    }\n\n    fn max(&self) -> Option<usize> {\n        Item::max(self)\n    }\n\n    fn set_max(&mut self, max: Option<Step>) -> Option<Step> {\n        Item::set_max(self, max)\n    }\n\n    fn set_name(&mut self, name: String) {\n        Item::set_name(self, name)\n    }\n\n    fn name(&self) -> Option<String> {\n        Item::name(self)\n    }\n\n    fn id(&self) -> Id {\n        Item::id(self)\n    }\n\n    fn message(&self, level: MessageLevel, message: String) {\n        Item::message(self, level, message)\n    }\n}\n\nimpl crate::NestedProgress for Item {\n    type SubProgress = Item;\n\n    fn add_child(&mut self, name: impl Into<String>) -> Self {\n        Item::add_child(self, name)\n    }\n\n    fn add_child_with_id(&mut self, name: impl Into<String>, id: Id) -> Self {\n        Item::add_child_with_id(self, name, id)\n    }\n}\n"
  },
  {
    "path": "src/tree/mod.rs",
    "content": "use crate::messages::MessageRingBuffer;\n\n/// The top-level of the progress tree.\n#[derive(Debug)]\npub struct Root {\n    pub(crate) inner: parking_lot::Mutex<Item>,\n}\n\n/// A `Tree` represents an element of the progress tree.\n///\n/// It can be used to set progress and send messages.\n/// ```rust\n/// let tree = prodash::tree::Root::new();\n/// let mut progress = tree.add_child(\"task 1\");\n///\n/// progress.init(Some(10), Some(\"elements\".into()));\n/// for p in 0..10 {\n///     progress.set(p);\n/// }\n/// progress.done(\"great success\");\n/// let mut  sub_progress = progress.add_child_with_id(\"sub-task 1\", *b\"TSK2\");\n/// sub_progress.init(None, None);\n/// sub_progress.set(5);\n/// sub_progress.fail(\"couldn't finish\");\n/// ```\npub struct Item {\n    pub(crate) key: crate::progress::Key,\n    pub(crate) value: crate::progress::StepShared,\n    pub(crate) highest_child_id: crate::progress::key::Id,\n    pub(crate) tree: std::sync::Arc<HashMap<crate::progress::Key, crate::progress::Task>>,\n    pub(crate) messages: std::sync::Arc<parking_lot::Mutex<MessageRingBuffer>>,\n}\n\n#[cfg(feature = \"dashmap\")]\ntype HashMap<K, V> = dashmap::DashMap<K, V>;\n\n#[cfg(not(feature = \"dashmap\"))]\ntype HashMap<K, V> = sync::HashMap<K, V>;\n\n#[cfg(not(feature = \"dashmap\"))]\npub(crate) mod sync {\n    pub struct HashMap<K, V>(parking_lot::Mutex<std::collections::HashMap<K, V>>);\n\n    impl<K, V> HashMap<K, V>\n    where\n        K: Eq + std::hash::Hash,\n    {\n        pub fn with_capacity(cap: usize) -> Self {\n            HashMap(parking_lot::Mutex::new(std::collections::HashMap::with_capacity(cap)))\n        }\n        pub fn extend_to(&self, out: &mut Vec<(K, V)>)\n        where\n            K: Clone,\n            V: Clone,\n        {\n            let lock = self.0.lock();\n            out.extend(lock.iter().map(|(k, v)| (k.clone(), v.clone())))\n        }\n        pub fn remove(&self, key: &K) -> Option<V> {\n            self.0.lock().remove(key)\n        }\n        pub fn get<T>(&self, key: &K, cb: impl FnOnce(&V) -> T) -> Option<T> {\n            self.0.lock().get(key).map(cb)\n        }\n        pub fn get_mut<T>(&self, key: &K, cb: impl FnOnce(&mut V) -> T) -> Option<T> {\n            self.0.lock().get_mut(key).map(cb)\n        }\n        pub fn insert(&self, key: K, value: V) {\n            self.0.lock().insert(key, value);\n        }\n        pub fn len(&self) -> usize {\n            self.0.lock().len()\n        }\n        pub fn clone(&self) -> Self\n        where\n            K: Clone,\n            V: Clone,\n        {\n            HashMap(parking_lot::Mutex::new(self.0.lock().clone()))\n        }\n    }\n}\n\nmod item;\n///\npub mod root;\n\n#[cfg(test)]\nmod tests;\n"
  },
  {
    "path": "src/tree/root.rs",
    "content": "use std::{\n    ops::Deref,\n    sync::{atomic::AtomicUsize, Arc, Weak},\n};\n\nuse parking_lot::Mutex;\n\nuse crate::{\n    messages::{Message, MessageCopyState, MessageRingBuffer},\n    progress::{Id, Key, Task},\n    tree::{Item, Root},\n};\n\nimpl Root {\n    /// Create a new tree with default configuration.\n    ///\n    /// As opposed to [Item](./struct.Item.html) instances, this type can be closed and sent\n    /// safely across threads.\n    pub fn new() -> Arc<Root> {\n        Options::default().into()\n    }\n\n    /// Returns the maximum amount of messages we can keep before overwriting older ones.\n    pub fn messages_capacity(&self) -> usize {\n        self.inner.lock().messages.lock().buf.capacity()\n    }\n\n    /// Returns the current amount of `Item`s stored in the tree.\n    /// **Note** that this is at most a guess as tasks can be added and removed in parallel.\n    pub fn num_tasks(&self) -> usize {\n        #[cfg(feature = \"progress-tree-hp-hashmap\")]\n        {\n            self.inner.lock().tree.len()\n        }\n        #[cfg(not(feature = \"progress-tree-hp-hashmap\"))]\n        {\n            self.inner.lock().tree.len()\n        }\n    }\n\n    /// Adds a new child `tree::Item`, whose parent is this instance, with the given `name`.\n    ///\n    /// This builds a hierarchy of `tree::Item`s, each having their own progress.\n    /// Use this method to [track progress](./struct.Item.html) of your first tasks.\n    pub fn add_child(&self, name: impl Into<String>) -> Item {\n        self.inner.lock().add_child(name)\n    }\n\n    /// Adds a new child `tree::Item`, whose parent is this instance, with the given `name` and `id`.\n    ///\n    /// This builds a hierarchy of `tree::Item`s, each having their own progress.\n    /// Use this method to [track progress](./struct.Item.html) of your first tasks.\n    pub fn add_child_with_id(&self, name: impl Into<String>, id: Id) -> Item {\n        self.inner.lock().add_child_with_id(name, id)\n    }\n\n    /// Copy the entire progress tree into the given `out` vector, so that\n    /// it can be traversed from beginning to end in order of hierarchy.\n    pub fn sorted_snapshot(&self, out: &mut Vec<(Key, Task)>) {\n        out.clear();\n        #[cfg(feature = \"progress-tree-hp-hashmap\")]\n        out.extend(self.inner.lock().tree.iter().map(|r| (*r.key(), r.value().clone())));\n        #[cfg(not(feature = \"progress-tree-hp-hashmap\"))]\n        self.inner.lock().tree.extend_to(out);\n        out.sort_by_key(|t| t.0);\n    }\n\n    /// Copy all messages from the internal ring buffer into the given `out`\n    /// vector. Messages are ordered from oldest to newest.\n    pub fn copy_messages(&self, out: &mut Vec<Message>) {\n        self.inner.lock().messages.lock().copy_all(out);\n    }\n\n    /// Copy only new messages from the internal ring buffer into the given `out`\n    /// vector. Messages are ordered from oldest to newest.\n    pub fn copy_new_messages(&self, out: &mut Vec<Message>, prev: Option<MessageCopyState>) -> MessageCopyState {\n        self.inner.lock().messages.lock().copy_new(out, prev)\n    }\n\n    /// Duplicate all content and return it.\n    ///\n    /// This is an expensive operation, whereas `clone()` is not as it is shallow.\n    pub fn deep_clone(&self) -> Arc<Root> {\n        Arc::new(Root {\n            inner: Mutex::new(self.inner.lock().deep_clone()),\n        })\n    }\n}\n\n/// A way to configure new [`tree::Root`](./tree/struct.Root.html) instances\n/// ```rust\n/// let tree = prodash::tree::root::Options::default().create();\n/// let tree2 = prodash::tree::root::Options { message_buffer_capacity: 100, ..Default::default() }.create();\n/// ```\n#[derive(Clone, Debug)]\npub struct Options {\n    /// The amount of [items][Item] the tree can hold without being forced to allocate.\n    pub initial_capacity: usize,\n    /// The amount of messages we can hold before we start overwriting old ones.\n    pub message_buffer_capacity: usize,\n}\n\nimpl Options {\n    /// Create a new [`Root`](./tree/struct.Root.html) instance from the\n    /// configuration within.\n    pub fn create(self) -> Root {\n        self.into()\n    }\n}\n\nimpl Default for Options {\n    fn default() -> Self {\n        Options {\n            initial_capacity: 100,\n            message_buffer_capacity: 20,\n        }\n    }\n}\n\nimpl From<Options> for Arc<Root> {\n    fn from(opts: Options) -> Self {\n        Arc::new(opts.into())\n    }\n}\n\nimpl From<Options> for Root {\n    fn from(\n        Options {\n            initial_capacity,\n            message_buffer_capacity,\n        }: Options,\n    ) -> Self {\n        Root {\n            inner: Mutex::new(Item {\n                highest_child_id: 0,\n                value: Arc::new(AtomicUsize::default()),\n                key: Key::default(),\n                tree: Arc::new(crate::tree::HashMap::with_capacity(initial_capacity)),\n                messages: Arc::new(Mutex::new(MessageRingBuffer::with_capacity(message_buffer_capacity))),\n            }),\n        }\n    }\n}\n\nimpl crate::WeakRoot for Weak<Root> {\n    type Root = Arc<Root>;\n\n    fn upgrade(&self) -> Option<Self::Root> {\n        Weak::upgrade(self)\n    }\n}\n\nimpl crate::Root for Arc<Root> {\n    type WeakRoot = Weak<Root>;\n\n    fn messages_capacity(&self) -> usize {\n        self.deref().messages_capacity()\n    }\n\n    fn num_tasks(&self) -> usize {\n        self.deref().num_tasks()\n    }\n\n    fn sorted_snapshot(&self, out: &mut Vec<(Key, Task)>) {\n        self.deref().sorted_snapshot(out)\n    }\n\n    fn copy_messages(&self, out: &mut Vec<Message>) {\n        self.deref().copy_messages(out)\n    }\n\n    fn copy_new_messages(&self, out: &mut Vec<Message>, prev: Option<MessageCopyState>) -> MessageCopyState {\n        self.deref().copy_new_messages(out, prev)\n    }\n\n    fn downgrade(&self) -> Self::WeakRoot {\n        Arc::downgrade(self)\n    }\n}\n"
  },
  {
    "path": "src/tree/tests.rs",
    "content": "mod message_buffer {\n    use crate::messages::{Message, MessageLevel, MessageRingBuffer};\n\n    fn push(buf: &mut MessageRingBuffer, msg: impl Into<String>) {\n        buf.push_overwrite(MessageLevel::Info, \"test\".into(), msg);\n    }\n    fn push_and_copy_all(buf: &mut MessageRingBuffer, msg: impl Into<String>, out: &mut Vec<Message>) {\n        push(buf, msg);\n        buf.copy_all(out);\n    }\n\n    fn assert_messages(actual: &[Message], expected: &[&'static str]) {\n        let actual: Vec<_> = actual.iter().map(|m| m.message.as_str()).collect();\n        assert_eq!(expected, actual.as_slice(), \"messages are ordered old to new\");\n    }\n\n    #[test]\n    fn copy_all() {\n        let mut buf = MessageRingBuffer::with_capacity(2);\n        let mut out = Vec::new();\n        buf.copy_all(&mut out);\n        assert_eq!(out, buf.buf);\n\n        push_and_copy_all(&mut buf, \"one\", &mut out);\n        assert_eq!(out, buf.buf);\n\n        push_and_copy_all(&mut buf, \"two\", &mut out);\n        assert_eq!(out, buf.buf);\n\n        push_and_copy_all(&mut buf, \"three\", &mut out);\n        assert_messages(&out, &[\"two\", \"three\"]);\n\n        push_and_copy_all(&mut buf, \"four\", &mut out);\n        assert_messages(&out, &[\"three\", \"four\"]);\n\n        push_and_copy_all(&mut buf, \"five\", &mut out);\n        buf.copy_all(&mut out);\n        assert_messages(&out, &[\"four\", \"five\"]);\n    }\n\n    mod copy_new {\n        use crate::{\n            messages::{Message, MessageCopyState, MessageRingBuffer},\n            tree::tests::message_buffer::{assert_messages, push},\n        };\n\n        #[test]\n        fn without_state() {\n            fn push_and_copy_new(buf: &mut MessageRingBuffer, msg: impl Into<String>, out: &mut Vec<Message>) {\n                push(buf, msg);\n                buf.copy_new(out, None);\n            }\n\n            let mut buf = MessageRingBuffer::with_capacity(2);\n            let mut out = Vec::new();\n            buf.copy_new(&mut out, None);\n            assert_eq!(out, buf.buf);\n\n            push_and_copy_new(&mut buf, \"one\", &mut out);\n            assert_eq!(out, buf.buf);\n\n            push_and_copy_new(&mut buf, \"two\", &mut out);\n            assert_eq!(out, buf.buf);\n\n            push_and_copy_new(&mut buf, \"three\", &mut out);\n            assert_messages(&out, &[\"two\", \"three\"]);\n        }\n\n        #[test]\n        fn with_continous_state() {\n            fn push_and_copy_new(\n                buf: &mut MessageRingBuffer,\n                msg: impl Into<String>,\n                out: &mut Vec<Message>,\n                state: Option<MessageCopyState>,\n            ) -> Option<MessageCopyState> {\n                push(buf, msg);\n                Some(buf.copy_new(out, state))\n            }\n            let mut buf = MessageRingBuffer::with_capacity(2);\n            let mut out = Vec::new();\n            let mut state = push_and_copy_new(&mut buf, \"one\", &mut out, None);\n            assert_eq!(out, buf.buf);\n\n            state = push_and_copy_new(&mut buf, \"two\", &mut out, state);\n            assert_messages(&out, &[\"two\"]);\n\n            state = push_and_copy_new(&mut buf, \"three\", &mut out, state);\n            assert_messages(&out, &[\"three\"]);\n\n            state = push_and_copy_new(&mut buf, \"four\", &mut out, state);\n            assert_messages(&out, &[\"four\"]);\n\n            push_and_copy_new(&mut buf, \"five\", &mut out, state);\n            assert_messages(&out, &[\"five\"]);\n\n            state = push_and_copy_new(&mut buf, \"six\", &mut out, None);\n            assert_messages(&out, &[\"five\", \"six\"]);\n\n            state = Some(buf.copy_new(&mut out, state));\n            assert_messages(&out, &[]);\n\n            push(&mut buf, \"seven\");\n            push(&mut buf, \"eight\");\n            state = Some(buf.copy_new(&mut out, state));\n            assert_messages(&out, &[\"seven\", \"eight\"]);\n\n            push(&mut buf, \"1\");\n            push(&mut buf, \"2\");\n            push(&mut buf, \"3\");\n            buf.copy_new(&mut out, state);\n            assert_messages(&out, &[\"2\", \"3\"]);\n        }\n    }\n}\n"
  },
  {
    "path": "src/unit/bytes.rs",
    "content": "use std::fmt;\n\nuse crate::{progress::Step, unit::DisplayValue};\n\n/// A marker for formatting numbers as bytes in renderers.\n#[derive(Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd, Debug)]\npub struct Bytes;\n\nimpl Bytes {\n    fn format_bytes(w: &mut dyn fmt::Write, value: Step) -> fmt::Result {\n        let string = bytesize::ByteSize(value as u64).display().si().to_string();\n        for token in string.split(' ') {\n            w.write_str(token)?;\n        }\n        Ok(())\n    }\n}\n\nimpl DisplayValue for Bytes {\n    fn display_current_value(&self, w: &mut dyn fmt::Write, value: Step, _upper: Option<Step>) -> fmt::Result {\n        Self::format_bytes(w, value)\n    }\n    fn display_upper_bound(&self, w: &mut dyn fmt::Write, upper_bound: Step, _value: Step) -> fmt::Result {\n        Self::format_bytes(w, upper_bound)\n    }\n\n    fn dyn_hash(&self, state: &mut dyn std::hash::Hasher) {\n        state.write(&[])\n    }\n\n    fn display_unit(&self, _w: &mut dyn fmt::Write, _value: Step) -> fmt::Result {\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/unit/display.rs",
    "content": "use std::fmt::{self, Write};\n\nuse crate::{\n    progress::Step,\n    unit::{DisplayValue, Unit},\n};\n\n/// The location at which [`Throughput`] or [`UnitDisplays`][UnitDisplay] should be placed.\n#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)]\n#[allow(missing_docs)]\npub enum Location {\n    BeforeValue,\n    AfterUnit,\n}\n\n/// A structure able to display throughput, a value change within a given duration.\n#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]\npub struct Throughput {\n    /// The change of value between the current value and the previous one.\n    pub value_change_in_timespan: Step,\n    /// The amount of time passed between the previous and the current value.\n    pub timespan: std::time::Duration,\n}\n\nimpl Throughput {\n    /// A convenience method to create a new ThroughPut from `value_change_in_timespan` and `timespan`.\n    pub fn new(value_change_in_timespan: Step, timespan: std::time::Duration) -> Self {\n        Throughput {\n            value_change_in_timespan,\n            timespan,\n        }\n    }\n}\n\n/// A way to display a [Unit].\n#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)]\npub struct Mode {\n    location: Location,\n    percent: bool,\n    throughput: bool,\n}\n\nimpl Mode {\n    fn percent_location(&self) -> Option<Location> {\n        if self.percent {\n            Some(self.location)\n        } else {\n            None\n        }\n    }\n\n    fn throughput_location(&self) -> Option<Location> {\n        if self.throughput {\n            Some(self.location)\n        } else {\n            None\n        }\n    }\n}\n\n/// initialization and modification\nimpl Mode {\n    /// Create a mode instance with percentage only.\n    pub fn with_percentage() -> Self {\n        Mode {\n            percent: true,\n            throughput: false,\n            location: Location::AfterUnit,\n        }\n    }\n    /// Create a mode instance with throughput only.\n    pub fn with_throughput() -> Self {\n        Mode {\n            percent: false,\n            throughput: true,\n            location: Location::AfterUnit,\n        }\n    }\n    /// Turn on percentage display on the current instance.\n    pub fn and_percentage(mut self) -> Self {\n        self.percent = true;\n        self\n    }\n    /// Turn on throughput display on the current instance.\n    pub fn and_throughput(mut self) -> Self {\n        self.throughput = true;\n        self\n    }\n    /// Change the display location to show up in front of the value.\n    pub fn show_before_value(mut self) -> Self {\n        self.location = Location::BeforeValue;\n        self\n    }\n}\n\n/// A utility to implement [Display][std::fmt::Display].\npub struct UnitDisplay<'a> {\n    pub(crate) current_value: Step,\n    pub(crate) upper_bound: Option<Step>,\n    pub(crate) throughput: Option<Throughput>,\n    pub(crate) parent: &'a Unit,\n    pub(crate) display: What,\n}\n\npub(crate) enum What {\n    ValuesAndUnit,\n    Unit,\n    Values,\n}\n\nimpl What {\n    fn values(&self) -> bool {\n        matches!(self, What::Values | What::ValuesAndUnit)\n    }\n    fn unit(&self) -> bool {\n        matches!(self, What::Unit | What::ValuesAndUnit)\n    }\n}\n\nimpl UnitDisplay<'_> {\n    /// Display everything, values and the unit.\n    pub fn all(&mut self) -> &Self {\n        self.display = What::ValuesAndUnit;\n        self\n    }\n    /// Display only values.\n    pub fn values(&mut self) -> &Self {\n        self.display = What::Values;\n        self\n    }\n    /// Display only units.\n    pub fn unit(&mut self) -> &Self {\n        self.display = What::Unit;\n        self\n    }\n}\n\nimpl fmt::Display for UnitDisplay<'_> {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        let unit: &dyn DisplayValue = self.parent.as_display_value();\n        let mode = self.parent.mode;\n\n        let percent_location_and_fraction = self.upper_bound.and_then(|upper| {\n            mode.and_then(|m| m.percent_location())\n                .map(|location| (location, ((self.current_value as f64 / upper as f64) * 100.0).floor()))\n        });\n        let throughput_and_location = self.throughput.as_ref().and_then(|throughput| {\n            mode.and_then(|m| m.throughput_location())\n                .map(|location| (location, throughput))\n        });\n        if self.display.values() {\n            if let Some((Location::BeforeValue, fraction)) = percent_location_and_fraction {\n                unit.display_percentage(f, fraction)?;\n                f.write_char(' ')?;\n            }\n            if let Some((Location::BeforeValue, throughput)) = throughput_and_location {\n                unit.display_throughput(f, throughput)?;\n                f.write_char(' ')?;\n            }\n            unit.display_current_value(f, self.current_value, self.upper_bound)?;\n            if let Some(upper) = self.upper_bound {\n                unit.separator(f, self.current_value, self.upper_bound)?;\n                unit.display_upper_bound(f, upper, self.current_value)?;\n            }\n        }\n        if self.display.unit() {\n            let mut buf = String::with_capacity(10);\n            if self.display.values() {\n                buf.write_char(' ')?;\n            }\n            unit.display_unit(&mut buf, self.current_value)?;\n            if buf.len() > 1 {\n                // did they actually write a unit?\n                f.write_str(&buf)?;\n            }\n\n            if let Some((Location::AfterUnit, fraction)) = percent_location_and_fraction {\n                f.write_char(' ')?;\n                unit.display_percentage(f, fraction)?;\n            }\n            if let Some((Location::AfterUnit, throughput)) = throughput_and_location {\n                f.write_char(' ')?;\n                unit.display_throughput(f, throughput)?;\n            }\n        }\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/unit/duration.rs",
    "content": "use std::fmt;\n\nuse crate::{progress::Step, unit::DisplayValue};\n\n/// A marker for formatting numbers as duration in renderers, as in `7d4h20m10s`.\n#[derive(Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd, Debug)]\npub struct Duration;\n\nimpl DisplayValue for Duration {\n    fn display_current_value(&self, w: &mut dyn fmt::Write, value: Step, _upper: Option<Step>) -> fmt::Result {\n        let dur = jiff::SignedDuration::from_secs(value as i64);\n        w.write_str(&format!(\"{dur:#}\"))\n    }\n    fn separator(&self, w: &mut dyn fmt::Write, _value: Step, _upper: Option<Step>) -> fmt::Result {\n        w.write_str(\" of \")\n    }\n    fn display_upper_bound(&self, w: &mut dyn fmt::Write, upper_bound: Step, _value: Step) -> fmt::Result {\n        let dur = jiff::SignedDuration::from_secs(upper_bound as i64);\n        w.write_str(&format!(\"{dur:#}\"))\n    }\n\n    fn dyn_hash(&self, state: &mut dyn std::hash::Hasher) {\n        state.write(&[])\n    }\n\n    fn display_unit(&self, _w: &mut dyn fmt::Write, _value: Step) -> fmt::Result {\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/unit/human.rs",
    "content": "use std::{fmt, fmt::Debug, hash::Hasher};\n\npub use human_format::{Formatter, Scales};\n\nuse crate::{progress::Step, unit::DisplayValue};\n\n/// A helper for formatting numbers in a format easily read by humans in renderers, as in `2.54 million objects`\n#[derive(Debug)]\npub struct Human {\n    /// The name of the represented unit, like 'items' or 'objects'.\n    pub name: &'static str,\n    /// The formatter to format the actual numbers.\n    pub formatter: Formatter,\n}\n\nimpl Human {\n    /// A convenience method to create a new new instance and its `formatter` and `name` fields.\n    pub fn new(formatter: Formatter, name: &'static str) -> Self {\n        Human { name, formatter }\n    }\n    fn format_bytes(&self, w: &mut dyn fmt::Write, value: Step) -> fmt::Result {\n        let string = self.formatter.format(value as f64);\n        for token in string.split(' ') {\n            w.write_str(token)?;\n        }\n        Ok(())\n    }\n}\n\nimpl DisplayValue for Human {\n    fn display_current_value(&self, w: &mut dyn fmt::Write, value: Step, _upper: Option<Step>) -> fmt::Result {\n        self.format_bytes(w, value)\n    }\n\n    fn display_upper_bound(&self, w: &mut dyn fmt::Write, upper_bound: Step, _value: Step) -> fmt::Result {\n        self.format_bytes(w, upper_bound)\n    }\n\n    fn dyn_hash(&self, state: &mut dyn Hasher) {\n        state.write(self.name.as_bytes());\n        state.write_u8(0);\n    }\n\n    fn display_unit(&self, w: &mut dyn fmt::Write, _value: Step) -> fmt::Result {\n        w.write_str(self.name)\n    }\n}\n"
  },
  {
    "path": "src/unit/mod.rs",
    "content": "use std::{fmt, ops::Deref, sync::Arc};\n\nuse crate::progress::Step;\n\n#[cfg(feature = \"unit-bytes\")]\nmod bytes;\n#[cfg(feature = \"unit-bytes\")]\npub use bytes::Bytes;\n\n#[cfg(feature = \"unit-duration\")]\nmod duration;\n#[cfg(feature = \"unit-duration\")]\npub use duration::Duration;\n\n#[cfg(feature = \"unit-human\")]\n///\npub mod human;\n#[cfg(feature = \"unit-human\")]\n#[doc(inline)]\npub use human::Human;\n\nmod range;\npub use range::Range;\n\nmod traits;\npub use traits::DisplayValue;\n\n/// Various utilities to display values and units.\npub mod display;\n\n/// A configurable and flexible unit for use in [Progress::init()][crate::Progress::init()].\n#[derive(Debug, Clone, Hash)]\npub struct Unit {\n    kind: Kind,\n    mode: Option<display::Mode>,\n}\n\n/// Either a static label or a dynamic one implementing [`DisplayValue`].\n#[derive(Clone)]\npub enum Kind {\n    /// Display only the given statically known label.\n    Label(&'static str),\n    /// Display a label created dynamically.\n    Dynamic(Arc<dyn DisplayValue + Send + Sync>),\n}\n\nimpl std::hash::Hash for Kind {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        match self {\n            Kind::Label(s) => {\n                0.hash(state);\n                s.dyn_hash(state)\n            }\n            Kind::Dynamic(label) => {\n                1.hash(state);\n                label.dyn_hash(state);\n            }\n        }\n    }\n}\n\nimpl fmt::Debug for Kind {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self {\n            Kind::Label(name) => f.write_fmt(format_args!(\"Unit::Label({:?})\", name)),\n            Kind::Dynamic(_) => f.write_fmt(format_args!(\"Unit::Dynamic(..)\")),\n        }\n    }\n}\n\nimpl From<&'static str> for Unit {\n    fn from(v: &'static str) -> Self {\n        label(v)\n    }\n}\n\n/// Returns a unit that is a static `label`.\npub fn label(label: &'static str) -> Unit {\n    Unit {\n        kind: Kind::Label(label),\n        mode: None,\n    }\n}\n\n/// Returns a unit that is a static `label` along with information on where to display a fraction and throughput.\npub fn label_and_mode(label: &'static str, mode: display::Mode) -> Unit {\n    Unit {\n        kind: Kind::Label(label),\n        mode: Some(mode),\n    }\n}\n\n/// Returns a unit that is a dynamic `label`.\npub fn dynamic(label: impl DisplayValue + Send + Sync + 'static) -> Unit {\n    Unit {\n        kind: Kind::Dynamic(Arc::new(label)),\n        mode: None,\n    }\n}\n\n/// Returns a unit that is a dynamic `label` along with information on where to display a fraction and throughput.\npub fn dynamic_and_mode(label: impl DisplayValue + Send + Sync + 'static, mode: display::Mode) -> Unit {\n    Unit {\n        kind: Kind::Dynamic(Arc::new(label)),\n        mode: Some(mode),\n    }\n}\n\n/// Display and utilities\nimpl Unit {\n    /// Create a representation of `self` implementing [`Display`][std::fmt::Display] in configurable fashion.\n    ///\n    /// * `current_value` is the progress value to display.\n    /// * `upper_bound` is the possibly available upper bound of `current_value`.\n    /// * `throughput` configures how throughput should be displayed if already available.\n    ///\n    /// Note that `throughput` is usually not available the first time a value is displayed.\n    pub fn display(\n        &self,\n        current_value: Step,\n        upper_bound: Option<Step>,\n        throughput: impl Into<Option<display::Throughput>>,\n    ) -> display::UnitDisplay<'_> {\n        display::UnitDisplay {\n            current_value,\n            upper_bound,\n            throughput: throughput.into(),\n            parent: self,\n            display: display::What::ValuesAndUnit,\n        }\n    }\n\n    /// Return `self` as trait object implementing `DisplayValue`.\n    pub fn as_display_value(&self) -> &dyn DisplayValue {\n        match self.kind {\n            Kind::Label(ref unit) => unit,\n            Kind::Dynamic(ref unit) => unit.deref(),\n        }\n    }\n}\n"
  },
  {
    "path": "src/unit/range.rs",
    "content": "use std::{fmt, hash::Hasher};\n\nuse crate::{progress::Step, unit::DisplayValue};\n\n/// A helper for formatting numbers representing ranges in renderers as in `2 of 5 steps`.\n#[derive(Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd, Debug)]\npub struct Range {\n    /// The name of the unit to be appended to the range.\n    pub name: &'static str,\n}\n\nimpl Range {\n    /// A convenience method to create a new instance of `name`.\n    pub fn new(name: &'static str) -> Self {\n        Range { name }\n    }\n}\n\nimpl DisplayValue for Range {\n    fn display_current_value(&self, w: &mut dyn fmt::Write, value: Step, _upper: Option<Step>) -> fmt::Result {\n        w.write_fmt(format_args!(\"{}\", value + 1))\n    }\n    fn separator(&self, w: &mut dyn fmt::Write, _value: Step, _upper: Option<Step>) -> fmt::Result {\n        w.write_str(\" of \")\n    }\n\n    fn dyn_hash(&self, state: &mut dyn Hasher) {\n        self.name.dyn_hash(state)\n    }\n\n    fn display_unit(&self, w: &mut dyn fmt::Write, _value: Step) -> fmt::Result {\n        w.write_str(self.name)\n    }\n}\n"
  },
  {
    "path": "src/unit/traits.rs",
    "content": "use std::{fmt, hash::Hasher};\n\nuse crate::{progress::Step, unit::display};\n\n/// A trait to encapsulate all capabilities needed to display a value with unit within a renderer.\npub trait DisplayValue {\n    /// Display the absolute `value` representing the current progress of an operation and write it to `w`.\n    ///\n    /// The `upper` bound is possibly provided when known to add context, even though it is not to be output\n    /// as part of this method call.\n    fn display_current_value(&self, w: &mut dyn fmt::Write, value: Step, _upper: Option<Step>) -> fmt::Result {\n        fmt::write(w, format_args!(\"{}\", value))\n    }\n    /// Emit a token to separate two values.\n    ///\n    /// The `value` and its `upper` bound are provided to add context, even though it is not to be output\n    /// as part of this method call.\n    fn separator(&self, w: &mut dyn fmt::Write, _value: Step, _upper: Option<Step>) -> fmt::Result {\n        w.write_str(\"/\")\n    }\n\n    /// Emit the `upper_bound` to `w`.\n    ///\n    /// The `value` is provided to add context, even though it is not to be output as part of this method call.\n    fn display_upper_bound(&self, w: &mut dyn fmt::Write, upper_bound: Step, _value: Step) -> fmt::Result {\n        fmt::write(w, format_args!(\"{}\", upper_bound))\n    }\n\n    /// A way to hash our state without using generics.\n    ///\n    /// This helps to determine quickly if something changed.\n    fn dyn_hash(&self, state: &mut dyn std::hash::Hasher);\n\n    /// Emit the unit of `value` to `w`.\n    ///\n    /// The `value` is provided to add context, even though it is not to be output as part of this method call.\n    fn display_unit(&self, w: &mut dyn fmt::Write, value: Step) -> fmt::Result;\n\n    /// Emit `percentage` to `w`.\n    fn display_percentage(&self, w: &mut dyn fmt::Write, percentage: f64) -> fmt::Result {\n        w.write_fmt(format_args!(\"[{}%]\", percentage as usize))\n    }\n\n    /// Emit the `throughput` of an operation to `w`.\n    fn display_throughput(&self, w: &mut dyn fmt::Write, throughput: &display::Throughput) -> fmt::Result {\n        let (fraction, unit) = self.fraction_and_time_unit(throughput.timespan);\n        w.write_char('|')?;\n        self.display_current_value(w, throughput.value_change_in_timespan, None)?;\n        w.write_char('/')?;\n        match fraction {\n            Some(fraction) => w.write_fmt(format_args!(\"{}\", fraction)),\n            None => Ok(()),\n        }?;\n        w.write_fmt(format_args!(\"{}|\", unit))\n    }\n\n    /// Given a `timespan`, return a fraction of the timespan based on the given unit, i.e. `(possible fraction, unit`).\n    fn fraction_and_time_unit(&self, timespan: std::time::Duration) -> (Option<f64>, &'static str) {\n        fn skip_one(v: f64) -> Option<f64> {\n            if (v - 1.0).abs() < f64::EPSILON {\n                None\n            } else {\n                Some(v)\n            }\n        }\n        const HOUR_IN_SECS: u64 = 60 * 60;\n        let secs = timespan.as_secs();\n        let h = secs / HOUR_IN_SECS;\n        if h > 0 {\n            return (skip_one(secs as f64 / HOUR_IN_SECS as f64), \"h\");\n        }\n        const MINUTES_IN_SECS: u64 = 60;\n        let m = secs / MINUTES_IN_SECS;\n        if m > 0 {\n            return (skip_one(secs as f64 / MINUTES_IN_SECS as f64), \"m\");\n        }\n        if secs > 0 {\n            return (skip_one(secs as f64), \"s\");\n        }\n\n        (skip_one(timespan.as_millis() as f64), \"ms\")\n    }\n}\n\nimpl DisplayValue for &'static str {\n    fn dyn_hash(&self, state: &mut dyn Hasher) {\n        state.write(self.as_bytes())\n    }\n\n    fn display_unit(&self, w: &mut dyn fmt::Write, _value: usize) -> fmt::Result {\n        w.write_fmt(format_args!(\"{}\", self))\n    }\n}\n"
  },
  {
    "path": "tests/nested_progress/key.rs",
    "content": "use prodash::progress::Key;\n\n#[test]\nfn size_in_memory() {\n    assert_eq!(std::mem::size_of::<Key>(), 24);\n}\n\nmod adjacency {\n    use prodash::progress::{\n        key::{Adjacency, SiblingLocation::*},\n        Key, Task,\n    };\n\n    fn to_kv(keys: &[Key]) -> Vec<(Key, Task)> {\n        let mut v: Vec<_> = keys.iter().map(|k| (k.to_owned(), Task::default())).collect();\n        v.sort_by_key(|v| v.0);\n        v\n    }\n\n    fn root_with_two_children() -> Vec<(Key, Task)> {\n        let r = Key::default();\n        to_kv(&[r.add_child(1), r.add_child(2)][..])\n    }\n    fn root_with_two_children_with_two_children() -> Vec<(Key, Task)> {\n        let r = Key::default();\n        let p1 = r.add_child(1);\n        let p2 = r.add_child(2);\n        to_kv(\n            &[\n                p1,\n                p1.add_child(1),\n                p1.add_child(2),\n                p2,\n                p2.add_child(1),\n                p2.add_child(2),\n            ][..],\n        )\n    }\n\n    fn root_with_three_levels() -> Vec<(Key, Task)> {\n        let r = Key::default();\n        let p1 = r.add_child(1);\n        let p2 = p1.add_child(2);\n        to_kv(&[p1, p2, p2.add_child(1)][..])\n    }\n\n    fn root_with_three_levels_two_siblings_on_level_2() -> Vec<(Key, Task)> {\n        let r = Key::default();\n        let p1 = r.add_child(1);\n        let p11 = p1.add_child(1);\n        let p12 = p1.add_child(2);\n        to_kv(&[p1, p11, p11.add_child(1), p12, p12.add_child(1)][..])\n    }\n\n    #[test]\n    fn root_level() {\n        let entries = root_with_two_children();\n        assert_eq!(\n            Key::adjacency(&entries, 0),\n            Adjacency(AboveAndBelow, NotFound, NotFound, NotFound, NotFound, NotFound)\n        );\n        assert_eq!(\n            Key::adjacency(&entries, 1),\n            Adjacency(Above, NotFound, NotFound, NotFound, NotFound, NotFound)\n        );\n    }\n\n    #[test]\n    fn level_2_two_siblings() {\n        let entries = root_with_two_children_with_two_children();\n        assert_eq!(\n            Key::adjacency(&entries, 0),\n            Adjacency(AboveAndBelow, NotFound, NotFound, NotFound, NotFound, NotFound)\n        );\n        {\n            assert_eq!(\n                Key::adjacency(&entries, 1),\n                Adjacency(AboveAndBelow, AboveAndBelow, NotFound, NotFound, NotFound, NotFound)\n            );\n            assert_eq!(\n                Key::adjacency(&entries, 2),\n                Adjacency(AboveAndBelow, Above, NotFound, NotFound, NotFound, NotFound)\n            );\n        }\n        assert_eq!(\n            Key::adjacency(&entries, 3),\n            Adjacency(Above, NotFound, NotFound, NotFound, NotFound, NotFound)\n        );\n        {\n            assert_eq!(\n                Key::adjacency(&entries, 4),\n                Adjacency(NotFound, AboveAndBelow, NotFound, NotFound, NotFound, NotFound)\n            );\n            assert_eq!(\n                Key::adjacency(&entries, 5),\n                Adjacency(NotFound, Above, NotFound, NotFound, NotFound, NotFound)\n            );\n        }\n    }\n\n    #[test]\n    fn level_3_single_sibling() {\n        let entries = root_with_three_levels();\n        assert_eq!(\n            Key::adjacency(&entries, 0),\n            Adjacency(Above, NotFound, NotFound, NotFound, NotFound, NotFound)\n        );\n        {\n            assert_eq!(\n                Key::adjacency(&entries, 1),\n                Adjacency(NotFound, Above, NotFound, NotFound, NotFound, NotFound)\n            );\n            {\n                assert_eq!(\n                    Key::adjacency(&entries, 2),\n                    Adjacency(NotFound, NotFound, Above, NotFound, NotFound, NotFound)\n                );\n            }\n        }\n    }\n\n    #[test]\n    fn level_3_two_siblings() {\n        let entries = root_with_three_levels_two_siblings_on_level_2();\n        {\n            assert_eq!(\n                Key::adjacency(&entries, 0),\n                Adjacency(Above, NotFound, NotFound, NotFound, NotFound, NotFound)\n            );\n            {\n                assert_eq!(\n                    Key::adjacency(&entries, 1),\n                    Adjacency(NotFound, AboveAndBelow, NotFound, NotFound, NotFound, NotFound)\n                );\n                {\n                    assert_eq!(\n                        Key::adjacency(&entries, 2),\n                        Adjacency(NotFound, AboveAndBelow, Above, NotFound, NotFound, NotFound)\n                    );\n                }\n\n                assert_eq!(\n                    Key::adjacency(&entries, 3),\n                    Adjacency(NotFound, Above, NotFound, NotFound, NotFound, NotFound)\n                );\n                {\n                    assert_eq!(\n                        Key::adjacency(&entries, 4),\n                        Adjacency(NotFound, NotFound, Above, NotFound, NotFound, NotFound)\n                    );\n                }\n            }\n        }\n    }\n\n    #[test]\n    fn orphaned_child_node() {\n        let mut entries = root_with_two_children();\n        entries.insert(\n            1,\n            (Key::default().add_child(0).add_child(0).add_child(1), Task::default()),\n        );\n        entries.sort_by_key(|v| v.0);\n        assert_eq!(\n            Key::adjacency(&entries, 0),\n            Adjacency(AboveAndBelow, NotFound, NotFound, NotFound, NotFound, NotFound),\n        );\n        assert_eq!(\n            Key::adjacency(&entries, 1),\n            Adjacency(AboveAndBelow, NotFound, NotFound, NotFound, NotFound, NotFound)\n        );\n        assert_eq!(\n            Key::adjacency(&entries, 2),\n            Adjacency(Above, NotFound, NotFound, NotFound, NotFound, NotFound)\n        );\n    }\n}\n"
  },
  {
    "path": "tests/nested_progress/mod.rs",
    "content": "mod key;\n"
  },
  {
    "path": "tests/prodash.rs",
    "content": "mod nested_progress;\nmod progress;\nmod unit;\n"
  },
  {
    "path": "tests/progress/mod.rs",
    "content": "use prodash::Progress;\n\n#[test]\nfn dyn_safe() {\n    fn needs_mut_dyn(_p: &mut dyn Progress) {}\n    fn needs_dyn(_p: &dyn Progress) {}\n    let root = prodash::tree::Root::new();\n    let mut child = root.add_child(\"hello\");\n    needs_mut_dyn(&mut child);\n    needs_dyn(&child);\n    let mut child_of_child = child.add_child(\"there\");\n    needs_mut_dyn(&mut child_of_child);\n    needs_dyn(&child);\n}\n\n#[test]\nfn thread_safe() {\n    fn needs_send_sync<'a, T: Sync + Send + 'a>(_p: T) {}\n    let root = prodash::tree::Root::new();\n    let mut child = root.add_child(\"hello\");\n    needs_send_sync(&child);\n    let child_of_child = child.add_child(\"there\");\n    needs_send_sync(&child_of_child);\n    needs_send_sync(child_of_child);\n    needs_send_sync(child);\n}\n"
  },
  {
    "path": "tests/unit/mod.rs",
    "content": "mod dynamic {\n    #[cfg(feature = \"unit-duration\")]\n    mod duration {\n        use prodash::unit::{self, Duration};\n\n        #[test]\n        fn value_and_upper_bound_use_own_unit() {\n            assert_eq!(\n                format!(\"{}\", unit::dynamic(Duration).display(40, Some(300), None)),\n                \"40s of 5m\"\n            );\n        }\n    }\n    #[cfg(feature = \"unit-human\")]\n    mod human {\n        use prodash::unit::{self, display, human, Human};\n\n        #[test]\n        fn various_combinations() {\n            let unit = unit::dynamic_and_mode(\n                Human::new(\n                    {\n                        let mut f = human::Formatter::new();\n                        f.with_decimals(1);\n                        f\n                    },\n                    \"objects\",\n                ),\n                display::Mode::with_percentage(),\n            );\n            assert_eq!(\n                format!(\"{}\", unit.display(100_002, Some(7_500_000), None)),\n                \"100.0k/7.5M objects [1%]\"\n            );\n            assert_eq!(format!(\"{}\", unit.display(100_002, None, None)), \"100.0k objects\");\n        }\n    }\n    mod range {\n        use prodash::unit::{self, display, Range};\n        #[test]\n        fn value_and_upper_bound_with_percentage() {\n            let unit = unit::dynamic_and_mode(Range::new(\"steps\"), display::Mode::with_percentage());\n            assert_eq!(format!(\"{}\", unit.display(0, Some(3), None)), \"1 of 3 steps [0%]\");\n            assert_eq!(format!(\"{}\", unit.display(1, Some(3), None)), \"2 of 3 steps [33%]\");\n            assert_eq!(format!(\"{}\", unit.display(2, Some(3), None)), \"3 of 3 steps [66%]\");\n        }\n    }\n    #[cfg(feature = \"unit-bytes\")]\n    mod bytes {\n        use prodash::unit::{self, display, Bytes};\n\n        #[test]\n        fn value_and_upper_bound_use_own_unit() {\n            assert_eq!(\n                format!(\n                    \"{}\",\n                    unit::dynamic_and_mode(Bytes, display::Mode::with_percentage()).display(\n                        1002,\n                        Some(10_000_000_000),\n                        None\n                    )\n                ),\n                \"1.0kB/10.0GB [0%]\"\n            );\n        }\n        #[test]\n        fn just_value() {\n            assert_eq!(format!(\"{}\", unit::dynamic(Bytes).display(5540, None, None)), \"5.5kB\");\n        }\n    }\n}\n\nmod label {\n    mod with_percentage {\n        mod only_values {\n            use prodash::unit::{self, display};\n            #[test]\n            fn display_current_value_with_upper_bound_percentage_before_value() {\n                assert_eq!(\n                    format!(\n                        \"{}\",\n                        unit::label_and_mode(\"items\", display::Mode::with_percentage().show_before_value())\n                            .display(123, Some(400), None)\n                            .values()\n                    ),\n                    \"[30%] 123/400\"\n                );\n            }\n        }\n\n        mod only_unit {\n            use prodash::unit::{self, display};\n            #[test]\n            fn display_current_value_with_upper_bound_percentage_after_unit() {\n                assert_eq!(\n                    format!(\n                        \"{}\",\n                        unit::label_and_mode(\"items\", display::Mode::with_percentage())\n                            .display(123, Some(400), None)\n                            .unit()\n                    ),\n                    \"items [30%]\"\n                );\n            }\n        }\n        use std::time;\n\n        use prodash::unit::{self, display};\n\n        #[test]\n        fn display_current_over_time_shows_throughput() {\n            let unit = unit::label_and_mode(\"items\", display::Mode::with_percentage().and_throughput());\n            assert_eq!(\n                format!(\"{}\", unit.display(123, None, None)),\n                \"123 items\",\n                \"from one measurement, there can be no throughput\"\n            );\n            assert_eq!(\n                format!(\n                    \"{}\",\n                    unit.display(\n                        500,\n                        None,\n                        display::Throughput::new(250, time::Duration::from_millis(500))\n                    )\n                ),\n                \"500 items |250/500ms|\",\n                \"sub-second intervals are displayed with millisecond precision\"\n            );\n            assert_eq!(\n                format!(\n                    \"{}\",\n                    unit.display(700, None, display::Throughput::new(500, time::Duration::from_secs(1)))\n                ),\n                \"700 items |500/s|\",\n                \"a '1' in the timespan is not displayed\"\n            );\n            assert_eq!(\n                format!(\n                    \"{}\",\n                    unit.display(500, None, display::Throughput::new(250, time::Duration::from_secs(30)))\n                ),\n                \"500 items |250/30s|\",\n                \"sub-minute intervals are displayed with second precision\"\n            );\n            assert_eq!(\n                format!(\n                    \"{}\",\n                    unit.display(700, None, display::Throughput::new(500, time::Duration::from_secs(60)))\n                ),\n                \"700 items |500/m|\",\n                \"it also knows minutes\"\n            );\n            let unit = unit::label_and_mode(\n                \"items\",\n                display::Mode::with_percentage().and_throughput().show_before_value(),\n            );\n            assert_eq!(\n                format!(\n                    \"{}\",\n                    unit.display(700, None, display::Throughput::new(500, time::Duration::from_secs(90)))\n                ),\n                \"|500/1.5m| 700 items\",\n                \"it uses fractions on the biggest possible unit\"\n            );\n            assert_eq!(\n                format!(\n                    \"{}\",\n                    unit.display(\n                        500,\n                        None,\n                        display::Throughput::new(250, time::Duration::from_secs(30 * 60))\n                    )\n                ),\n                \"|250/30m| 500 items\",\n                \"sub-hour intervals are displayed with minute precision\"\n            );\n            assert_eq!(\n                format!(\n                    \"{}\",\n                    unit.display(\n                        700,\n                        None,\n                        display::Throughput::new(500, time::Duration::from_secs(60 * 60))\n                    )\n                ),\n                \"|500/h| 700 items\",\n                \"it also knows hours\"\n            );\n        }\n\n        #[test]\n        fn display_current_value_no_upper_bound_shows_no_percentage() {\n            assert_eq!(\n                format!(\n                    \"{}\",\n                    unit::label_and_mode(\"items\", display::Mode::with_percentage()).display(123, None, None)\n                ),\n                \"123 items\"\n            );\n        }\n        #[test]\n        fn display_current_value_with_upper_bound_shows_percentage() {\n            assert_eq!(\n                format!(\n                    \"{}\",\n                    unit::label_and_mode(\"items\", display::Mode::with_percentage()).display(123, Some(500), None)\n                ),\n                \"123/500 items [24%]\"\n            );\n            assert_eq!(\n                format!(\n                    \"{}\",\n                    unit::label_and_mode(\"items\", display::Mode::with_percentage().show_before_value()).display(\n                        123,\n                        Some(500),\n                        None\n                    )\n                ),\n                \"[24%] 123/500 items\"\n            );\n        }\n    }\n    mod without_percentage {\n        use prodash::unit;\n\n        #[test]\n        fn display_current_value_no_upper_bound() {\n            assert_eq!(\n                format!(\"{}\", unit::label(\"items\").display(123, None, None)),\n                \"123 items\"\n            );\n        }\n        #[test]\n        fn display_current_value_with_upper_bound() {\n            assert_eq!(\n                format!(\"{}\", unit::label(\"items\").display(123, Some(500), None)),\n                \"123/500 items\"\n            );\n        }\n    }\n}\n\nmod size {\n    use std::mem::size_of;\n\n    use prodash::unit::{display, Unit};\n\n    #[test]\n    fn of_mode() {\n        assert_eq!(size_of::<display::Mode>(), 3);\n    }\n    #[test]\n    fn of_unit() {\n        assert_eq!(size_of::<Unit>(), 32);\n    }\n}\n"
  }
]