├── .gitignore ├── .cargo └── config.toml ├── committed.toml ├── release.toml ├── .pre-commit-config.yaml ├── .github ├── workflows │ ├── spelling.yml │ ├── pre-commit.yml │ ├── committed.yml │ ├── audit.yml │ ├── rust-next.yml │ └── ci.yml ├── settings.yml └── renovate.json5 ├── .clippy.toml ├── examples ├── tree.rs └── pretty-test.rs ├── LICENSE-MIT ├── README.md ├── CHANGELOG.md ├── src ├── tests.rs └── lib.rs ├── CONTRIBUTING.md ├── Cargo.toml ├── Cargo.lock └── deny.toml /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [resolver] 2 | incompatible-rust-versions = "fallback" 3 | -------------------------------------------------------------------------------- /committed.toml: -------------------------------------------------------------------------------- 1 | style="conventional" 2 | ignore_author_re="(dependabot|renovate)" 3 | merge_commit = false 4 | -------------------------------------------------------------------------------- /release.toml: -------------------------------------------------------------------------------- 1 | owners = ["github:rust-cli:Maintainers"] 2 | dependent-version = "fix" 3 | allow-branch = ["main"] 4 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | default_install_hook_types: ["pre-commit", "commit-msg"] 2 | repos: 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v5.0.0 5 | hooks: 6 | - id: check-yaml 7 | - id: check-json 8 | - id: check-toml 9 | - id: check-merge-conflict 10 | - id: check-case-conflict 11 | - id: detect-private-key 12 | - repo: https://github.com/crate-ci/typos 13 | rev: v1.32.0 14 | hooks: 15 | - id: typos 16 | - repo: https://github.com/crate-ci/committed 17 | rev: v1.1.7 18 | hooks: 19 | - id: committed 20 | -------------------------------------------------------------------------------- /.github/workflows/spelling.yml: -------------------------------------------------------------------------------- 1 | name: Spelling 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: [pull_request] 7 | 8 | env: 9 | RUST_BACKTRACE: 1 10 | CARGO_TERM_COLOR: always 11 | CLICOLOR: 1 12 | 13 | concurrency: 14 | group: "${{ github.workflow }}-${{ github.ref }}" 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | spelling: 19 | name: Spell Check with Typos 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Checkout Actions Repository 23 | uses: actions/checkout@v6 24 | - name: Spell Check Repo 25 | uses: crate-ci/typos@master 26 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yml: -------------------------------------------------------------------------------- 1 | name: pre-commit 2 | 3 | permissions: {} # none 4 | 5 | on: 6 | pull_request: 7 | push: 8 | branches: [main] 9 | 10 | env: 11 | RUST_BACKTRACE: 1 12 | CARGO_TERM_COLOR: always 13 | CLICOLOR: 1 14 | 15 | concurrency: 16 | group: "${{ github.workflow }}-${{ github.ref }}" 17 | cancel-in-progress: true 18 | 19 | jobs: 20 | pre-commit: 21 | permissions: 22 | contents: read 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v6 26 | - uses: actions/setup-python@v6 27 | with: 28 | python-version: '3.x' 29 | - uses: pre-commit/action@v3.0.1 30 | -------------------------------------------------------------------------------- /.github/workflows/committed.yml: -------------------------------------------------------------------------------- 1 | # Not run as part of pre-commit checks because they don't handle sending the correct commit 2 | # range to `committed` 3 | name: Lint Commits 4 | on: [pull_request] 5 | 6 | permissions: 7 | contents: read 8 | 9 | env: 10 | RUST_BACKTRACE: 1 11 | CARGO_TERM_COLOR: always 12 | CLICOLOR: 1 13 | 14 | concurrency: 15 | group: "${{ github.workflow }}-${{ github.ref }}" 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | committed: 20 | name: Lint Commits 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Checkout Actions Repository 24 | uses: actions/checkout@v6 25 | with: 26 | fetch-depth: 0 27 | - name: Lint Commits 28 | uses: crate-ci/committed@master 29 | -------------------------------------------------------------------------------- /.clippy.toml: -------------------------------------------------------------------------------- 1 | allow-print-in-tests = true 2 | allow-expect-in-tests = true 3 | allow-unwrap-in-tests = true 4 | allow-dbg-in-tests = true 5 | disallowed-methods = [ 6 | { path = "std::option::Option::map_or", reason = "prefer `map(..).unwrap_or(..)` for legibility" }, 7 | { path = "std::option::Option::map_or_else", reason = "prefer `map(..).unwrap_or_else(..)` for legibility" }, 8 | { path = "std::result::Result::map_or", reason = "prefer `map(..).unwrap_or(..)` for legibility" }, 9 | { path = "std::result::Result::map_or_else", reason = "prefer `map(..).unwrap_or_else(..)` for legibility" }, 10 | { path = "std::iter::Iterator::for_each", reason = "prefer `for` for side-effects" }, 11 | { path = "std::iter::Iterator::try_for_each", reason = "prefer `for` for side-effects" }, 12 | ] 13 | -------------------------------------------------------------------------------- /examples/tree.rs: -------------------------------------------------------------------------------- 1 | use termtree::Tree; 2 | 3 | use std::path::Path; 4 | use std::{env, fs, io}; 5 | 6 | fn label>(p: P) -> String { 7 | p.as_ref().file_name().unwrap().to_str().unwrap().to_owned() 8 | } 9 | 10 | fn tree>(p: P) -> io::Result> { 11 | let result = fs::read_dir(&p)?.filter_map(|e| e.ok()).fold( 12 | Tree::new(label(p.as_ref().canonicalize()?)), 13 | |mut root, entry| { 14 | let dir = entry.metadata().unwrap(); 15 | if dir.is_dir() { 16 | root.push(tree(entry.path()).unwrap()); 17 | } else { 18 | root.push(Tree::new(label(entry.path()))); 19 | } 20 | root 21 | }, 22 | ); 23 | Ok(result) 24 | } 25 | 26 | fn main() { 27 | let dir = env::args().nth(1).unwrap_or_else(|| String::from(".")); 28 | match tree(dir) { 29 | Ok(tree) => println!("{tree}"), 30 | Err(err) => println!("error: {err}"), 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) Individual contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /.github/workflows/audit.yml: -------------------------------------------------------------------------------- 1 | name: Security audit 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | pull_request: 8 | paths: 9 | - '**/Cargo.toml' 10 | - '**/Cargo.lock' 11 | push: 12 | branches: 13 | - main 14 | 15 | env: 16 | RUST_BACKTRACE: 1 17 | CARGO_TERM_COLOR: always 18 | CLICOLOR: 1 19 | 20 | concurrency: 21 | group: "${{ github.workflow }}-${{ github.ref }}" 22 | cancel-in-progress: true 23 | 24 | jobs: 25 | security_audit: 26 | permissions: 27 | issues: write # to create issues (actions-rs/audit-check) 28 | checks: write # to create check (actions-rs/audit-check) 29 | runs-on: ubuntu-latest 30 | # Prevent sudden announcement of a new advisory from failing ci: 31 | continue-on-error: true 32 | steps: 33 | - name: Checkout repository 34 | uses: actions/checkout@v6 35 | - uses: actions-rs/audit-check@v1 36 | with: 37 | token: ${{ secrets.GITHUB_TOKEN }} 38 | 39 | cargo_deny: 40 | permissions: 41 | issues: write # to create issues (actions-rs/audit-check) 42 | checks: write # to create check (actions-rs/audit-check) 43 | runs-on: ubuntu-latest 44 | strategy: 45 | matrix: 46 | checks: 47 | - bans licenses sources 48 | steps: 49 | - uses: actions/checkout@v6 50 | - uses: EmbarkStudios/cargo-deny-action@v2 51 | with: 52 | command: check ${{ matrix.checks }} 53 | rust-version: stable 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # termtree [![Main](https://github.com/rust-cli/termtree/actions/workflows/ci.yml/badge.svg)](https://github.com/rust-cli/termtree/actions/workflows/ci.yml) 2 | 3 | > Visualize tree-like data on the command-line 4 | 5 | [API documentation](https://docs.rs/termtree) 6 | 7 | ## Example 8 | 9 | An example program is provided under the "examples" directory to mimic the `tree(1)` 10 | linux program 11 | 12 | ```bash 13 | $ cargo run --example tree target 14 | Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs 15 | Running `target/debug/examples/tree target` 16 | target 17 | └── debug 18 | ├── .cargo-lock 19 | ├── .fingerprint 20 | | └── termtree-21a5bdbd42e0b6da 21 | | ├── dep-example-tree 22 | | ├── dep-lib-termtree 23 | | ├── example-tree 24 | | ├── example-tree.json 25 | | ├── lib-termtree 26 | | └── lib-termtree.json 27 | ├── build 28 | ├── deps 29 | | └── libtermtree.rlib 30 | ├── examples 31 | | ├── tree 32 | | └── tree.dSYM 33 | | └── Contents 34 | | ├── Info.plist 35 | | └── Resources 36 | | └── DWARF 37 | | └── tree 38 | ├── libtermtree.rlib 39 | └── native 40 | ``` 41 | 42 | ## Related Crates 43 | 44 | - [`treeline`](https://crates.io/crates/treeline): termtree was forked from this. 45 | - [`tree_decorator`](https://crates.io/crates/tree_decorator) 46 | - [`xtree`](https://crates.io/crates/xtree) 47 | - [`ptree`](https://crates.io/crates/ptree) 48 | 49 | ## License 50 | 51 | Licensed under MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 52 | -------------------------------------------------------------------------------- /.github/workflows/rust-next.yml: -------------------------------------------------------------------------------- 1 | name: rust-next 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | schedule: 8 | - cron: '16 17 16 * *' 9 | 10 | env: 11 | RUST_BACKTRACE: 1 12 | CARGO_TERM_COLOR: always 13 | CLICOLOR: 1 14 | 15 | concurrency: 16 | group: "${{ github.workflow }}-${{ github.ref }}" 17 | cancel-in-progress: true 18 | 19 | jobs: 20 | test: 21 | name: Test 22 | strategy: 23 | matrix: 24 | os: ["ubuntu-latest", "windows-latest", "macos-latest"] 25 | rust: ["stable", "beta"] 26 | include: 27 | - os: ubuntu-latest 28 | rust: "nightly" 29 | continue-on-error: ${{ matrix.rust != 'stable' }} 30 | runs-on: ${{ matrix.os }} 31 | steps: 32 | - name: Checkout repository 33 | uses: actions/checkout@v6 34 | - name: Install Rust 35 | uses: dtolnay/rust-toolchain@stable 36 | with: 37 | toolchain: ${{ matrix.rust }} 38 | - uses: Swatinem/rust-cache@v2 39 | - uses: taiki-e/install-action@cargo-hack 40 | - name: Build 41 | run: cargo test --workspace --no-run 42 | - name: Test 43 | run: cargo hack test --each-feature --workspace 44 | latest: 45 | name: "Check latest dependencies" 46 | runs-on: ubuntu-latest 47 | steps: 48 | - name: Checkout repository 49 | uses: actions/checkout@v6 50 | - name: Install Rust 51 | uses: dtolnay/rust-toolchain@stable 52 | with: 53 | toolchain: stable 54 | - uses: Swatinem/rust-cache@v2 55 | - uses: taiki-e/install-action@cargo-hack 56 | - name: Update dependencies 57 | run: cargo update 58 | - name: Build 59 | run: cargo test --workspace --no-run 60 | - name: Test 61 | run: cargo hack test --each-feature --workspace 62 | -------------------------------------------------------------------------------- /.github/settings.yml: -------------------------------------------------------------------------------- 1 | # These settings are synced to GitHub by https://probot.github.io/apps/settings/ 2 | 3 | repository: 4 | description: "Visualize tree-like data on the command-line" 5 | homepage: "https://docs.rs/termtree" 6 | topics: "rust cli" 7 | has_issues: true 8 | has_projects: false 9 | has_wiki: false 10 | has_downloads: true 11 | default_branch: main 12 | 13 | # Preference: people do clean commits 14 | allow_merge_commit: true 15 | # Backup in case we need to clean up commits 16 | allow_squash_merge: true 17 | # Not really needed 18 | allow_rebase_merge: false 19 | 20 | allow_auto_merge: true 21 | delete_branch_on_merge: true 22 | 23 | squash_merge_commit_title: "PR_TITLE" 24 | squash_merge_commit_message: "PR_BODY" 25 | merge_commit_message: "PR_BODY" 26 | 27 | labels: 28 | # Type 29 | - name: bug 30 | color: '#b60205' 31 | description: "Not as expected" 32 | - name: enhancement 33 | color: '#1d76db' 34 | description: "Improve the expected" 35 | # Flavor 36 | - name: question 37 | color: "#cc317c" 38 | description: "Uncertainty is involved" 39 | - name: breaking-change 40 | color: "#e99695" 41 | - name: good first issue 42 | color: '#c2e0c6' 43 | description: "Help wanted!" 44 | 45 | # This serves more as documentation. 46 | # Branch protection API was replaced by rulesets but settings isn't updated. 47 | # See https://github.com/repository-settings/app/issues/825 48 | # 49 | # branches: 50 | # - name: main 51 | # protection: 52 | # required_pull_request_reviews: null 53 | # required_conversation_resolution: true 54 | # required_status_checks: 55 | # # Required. Require branches to be up to date before merging. 56 | # strict: false 57 | # contexts: ["CI", "Spell Check with Typos"] 58 | # enforce_admins: false 59 | # restrictions: null 60 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/). 6 | 7 | 8 | ## [Unreleased] - ReleaseDate 9 | 10 | ## [0.5.1] - 2024-07-25 11 | 12 | ## [0.5.0] - 2024-07-09 13 | 14 | ### Compatibility 15 | 16 | - MSRV bumped to 1.74.0 17 | 18 | ### Breaking Changes 19 | 20 | - Inherit glyphs from the parent node 21 | 22 | ### Features 23 | 24 | - Inherit glyphs from the parent node 25 | 26 | ### Fixes 27 | 28 | - Track glyphs correctly for "skip" lines 29 | 30 | ## [0.4.1] - 2023-03-14 31 | 32 | ### Fixes 33 | 34 | - Pass `f.alternate()` down to the nodes 35 | 36 | ## [0.4.0] - 2022-04-07 37 | 38 | ### Compatibility 39 | 40 | - Removed functions in place of direct member access 41 | 42 | ### Features 43 | 44 | - Directly expose `root` and `leaves` 45 | 46 | ## [0.3.0] - 2022-04-07 47 | 48 | ### Compatibility 49 | 50 | - MSRV bumped to 1.54.0 51 | - `Tree::new` removed 52 | - `Tree::root` renamed to `Tree::new` 53 | 54 | ### Features 55 | 56 | - Accessors for roots and leaves 57 | - Shortcuts for building up a `Tree` 58 | 59 | ## [0.2.4] - 2022-01-07 60 | 61 | #### Fixes 62 | 63 | - Adjusted to a cleaner looking continuous middle skip 64 | 65 | ## [0.2.3] - 2021-10-26 66 | 67 | #### Features 68 | 69 | - Provide customization of tree glyphs 70 | 71 | ## [0.2.2] - 2021-10-22 72 | 73 | #### Fixes 74 | 75 | - Do not overflow the stack 76 | 77 | ## [0.2.1] - 2021-10-07 78 | 79 | #### Features 80 | 81 | - Add leaves via `Extend::extend` 82 | - Opt-in multi-line item support 83 | 84 | ## [0.2.0] - 2021-10-07 85 | 86 | Forked from `treeline` 87 | 88 | ## 0.1.0 89 | 90 | * initial release 91 | 92 | 93 | [Unreleased]: https://github.com/rust-cli/termtree/compare/v0.5.1...HEAD 94 | [0.5.1]: https://github.com/rust-cli/termtree/compare/v0.5.0...v0.5.1 95 | [0.5.0]: https://github.com/rust-cli/termtree/compare/v0.4.1...v0.5.0 96 | [0.4.1]: https://github.com/rust-cli/termtree/compare/v0.4.0...v0.4.1 97 | [0.4.0]: https://github.com/rust-cli/termtree/compare/v0.3.0...v0.4.0 98 | [0.3.0]: https://github.com/rust-cli/termtree/compare/v0.2.4...v0.3.0 99 | [0.2.4]: https://github.com/rust-cli/termtree/compare/v0.2.3...v0.2.4 100 | [0.2.3]: https://github.com/rust-cli/termtree/compare/v0.2.2...v0.2.3 101 | [0.2.2]: https://github.com/rust-cli/termtree/compare/v0.2.1...v0.2.2 102 | [0.2.1]: https://github.com/rust-cli/termtree/compare/v0.2.0...v0.2.1 103 | [0.2.0]: https://github.com/assert-rs/assert_cmd/compare/v0.1.0...v0.2.0 104 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | schedule: [ 3 | 'before 5am on the first day of the month', 4 | ], 5 | semanticCommits: 'enabled', 6 | commitMessageLowerCase: 'never', 7 | configMigration: true, 8 | dependencyDashboard: true, 9 | customManagers: [ 10 | { 11 | customType: 'regex', 12 | managerFilePatterns: [ 13 | '/^rust-toolchain\\.toml$/', 14 | '/Cargo.toml$/', 15 | '/clippy.toml$/', 16 | '/\\.clippy.toml$/', 17 | '/^\\.github/workflows/ci.yml$/', 18 | '/^\\.github/workflows/rust-next.yml$/', 19 | ], 20 | matchStrings: [ 21 | 'STABLE.*?(?\\d+\\.\\d+(\\.\\d+)?)', 22 | '(?\\d+\\.\\d+(\\.\\d+)?).*?STABLE', 23 | ], 24 | depNameTemplate: 'STABLE', 25 | packageNameTemplate: 'rust-lang/rust', 26 | datasourceTemplate: 'github-releases', 27 | }, 28 | ], 29 | packageRules: [ 30 | { 31 | commitMessageTopic: 'Rust Stable', 32 | matchManagers: [ 33 | 'custom.regex', 34 | ], 35 | matchDepNames: [ 36 | 'STABLE', 37 | ], 38 | extractVersion: '^(?\\d+\\.\\d+)', // Drop the patch version 39 | schedule: [ 40 | '* * * * *', 41 | ], 42 | automerge: true, 43 | }, 44 | // Goals: 45 | // - Keep version reqs low, ignoring compatible normal/build dependencies 46 | // - Take advantage of latest dev-dependencies 47 | // - Rollup safe upgrades to reduce CI runner load 48 | // - Help keep number of versions down by always using latest breaking change 49 | // - Have lockfile and manifest in-sync 50 | { 51 | matchManagers: [ 52 | 'cargo', 53 | ], 54 | matchDepTypes: [ 55 | 'build-dependencies', 56 | 'dependencies', 57 | ], 58 | matchCurrentVersion: '>=0.1.0', 59 | matchUpdateTypes: [ 60 | 'patch', 61 | ], 62 | enabled: false, 63 | }, 64 | { 65 | matchManagers: [ 66 | 'cargo', 67 | ], 68 | matchDepTypes: [ 69 | 'build-dependencies', 70 | 'dependencies', 71 | ], 72 | matchCurrentVersion: '>=1.0.0', 73 | matchUpdateTypes: [ 74 | 'minor', 75 | 'patch', 76 | ], 77 | enabled: false, 78 | }, 79 | { 80 | matchManagers: [ 81 | 'cargo', 82 | ], 83 | matchDepTypes: [ 84 | 'dev-dependencies', 85 | ], 86 | matchCurrentVersion: '>=0.1.0', 87 | matchUpdateTypes: [ 88 | 'patch', 89 | ], 90 | automerge: true, 91 | groupName: 'compatible (dev)', 92 | }, 93 | { 94 | matchManagers: [ 95 | 'cargo', 96 | ], 97 | matchDepTypes: [ 98 | 'dev-dependencies', 99 | ], 100 | matchCurrentVersion: '>=1.0.0', 101 | matchUpdateTypes: [ 102 | 'minor', 103 | 'patch', 104 | ], 105 | automerge: true, 106 | groupName: 'compatible (dev)', 107 | }, 108 | ], 109 | } 110 | -------------------------------------------------------------------------------- /examples/pretty-test.rs: -------------------------------------------------------------------------------- 1 | //! Make the output of `cargo test` prettier in tree style. 2 | //! To see the complete demo coupling with rust-script, you can refer to 3 | //! 4 | 5 | use std::collections::{btree_map::Entry, BTreeMap}; 6 | use termtree::{GlyphPalette, Tree}; 7 | 8 | fn main() { 9 | let text = " 10 | test a::b::c ... FAILED 11 | test a::d ... ok 12 | test works ... ok 13 | "; 14 | assert_eq!( 15 | pretty_test(text.trim().lines()).unwrap().to_string(), 16 | "\ 17 | test 18 | ├── a 19 | │ ├── b 20 | │ │ └─ ❌ c 21 | │ └─ ✅ d 22 | └─ ✅ works\n" 23 | ); 24 | } 25 | 26 | #[derive(Debug)] 27 | enum Node<'s> { 28 | Path(BTreeMap<&'s str, Node<'s>>), 29 | Status(&'s str), 30 | } 31 | 32 | fn pretty_test<'s>(lines: impl Iterator) -> Option> { 33 | let mut path = BTreeMap::new(); 34 | for line in lines { 35 | let mut iter = line.splitn(3, ' '); 36 | let mut split = iter.nth(1)?.split("::"); 37 | let next = split.next(); 38 | let status = iter.next()?; 39 | make_node(split, status, &mut path, next); 40 | } 41 | let mut tree = Tree::new("test"); 42 | for (root, child) in path { 43 | make_tree(root, &child, &mut tree); 44 | } 45 | Some(tree) 46 | } 47 | 48 | // Add paths to Node 49 | fn make_node<'s>( 50 | mut split: impl Iterator, 51 | status: &'s str, 52 | path: &mut BTreeMap<&'s str, Node<'s>>, 53 | key: Option<&'s str>, 54 | ) { 55 | let Some(key) = key else { return }; 56 | let next = split.next(); 57 | match path.entry(key) { 58 | Entry::Vacant(empty) => { 59 | if next.is_some() { 60 | let mut btree = BTreeMap::new(); 61 | make_node(split, status, &mut btree, next); 62 | empty.insert(Node::Path(btree)); 63 | } else { 64 | empty.insert(Node::Status(status)); 65 | } 66 | } 67 | Entry::Occupied(mut btree) => { 68 | if let Node::Path(btree) = btree.get_mut() { 69 | make_node(split, status, btree, next); 70 | } 71 | } 72 | } 73 | } 74 | 75 | // Add Node to Tree 76 | fn make_tree<'s>(root: &'s str, node: &Node<'s>, parent: &mut Tree<&'s str>) { 77 | match node { 78 | Node::Path(btree) => { 79 | let mut t = Tree::new(root); 80 | for (path, child) in btree { 81 | make_tree(path, child, &mut t); 82 | } 83 | parent.push(t); 84 | } 85 | Node::Status(s) => { 86 | parent.push(Tree::new(root).with_glyphs(set_status(s))); 87 | } 88 | } 89 | } 90 | 91 | // Display with a status icon 92 | fn set_status(status: &str) -> GlyphPalette { 93 | let mut glyph = GlyphPalette::new(); 94 | glyph.item_indent = if status.ends_with("ok") { 95 | "─ ✅ " 96 | } else { 97 | "─ ❌ " 98 | }; 99 | glyph 100 | } 101 | -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | use snapbox::assert_data_eq; 2 | use snapbox::str; 3 | 4 | use super::*; 5 | 6 | #[test] 7 | fn render_tree_root() { 8 | let tree = Tree::new("foo"); 9 | assert_data_eq!( 10 | format!("{}", tree), 11 | str![[r#" 12 | foo 13 | 14 | "#]] 15 | ); 16 | } 17 | 18 | #[test] 19 | fn render_tree_with_leaves() { 20 | let tree = Tree::new("foo").with_leaves([Tree::new("bar").with_leaves(["baz"])]); 21 | assert_data_eq!( 22 | format!("{}", tree), 23 | str![[r#" 24 | foo 25 | └── bar 26 | └── baz 27 | 28 | "#]] 29 | ); 30 | } 31 | 32 | #[test] 33 | fn render_tree_with_multiple_leaves() { 34 | let tree = Tree::new("foo").with_leaves(["bar", "baz"]); 35 | assert_data_eq!( 36 | format!("{}", tree), 37 | str![[r#" 38 | foo 39 | ├── bar 40 | └── baz 41 | 42 | "#]] 43 | ); 44 | } 45 | 46 | #[test] 47 | fn render_tree_with_multiline_leaf() { 48 | let tree = Tree::new("foo").with_leaves([ 49 | Tree::new("hello\nworld").with_multiline(true), 50 | Tree::new("goodbye\nworld").with_multiline(true), 51 | ]); 52 | assert_data_eq!( 53 | format!("{}", tree), 54 | str![[r#" 55 | foo 56 | ├── hello 57 | │ world 58 | └── goodbye 59 | world 60 | 61 | "#]] 62 | ); 63 | } 64 | 65 | #[test] 66 | fn render_custom_glyphs() { 67 | let root = GlyphPalette { 68 | middle_item: "[mid ]", 69 | last_item: "[last ]", 70 | item_indent: "[indent ]", 71 | 72 | middle_skip: "[mskip]", 73 | last_skip: "[lskip]", 74 | skip_indent: "[iskip ]", 75 | }; 76 | let middle = GlyphPalette { 77 | middle_item: "(mid )", 78 | last_item: "(last )", 79 | item_indent: "(indent )", 80 | 81 | middle_skip: "(mskip)", 82 | last_skip: "(lskip)", 83 | skip_indent: "(iskip )", 84 | }; 85 | 86 | let tree = Tree::new("node 1").with_glyphs(root).with_leaves([ 87 | Tree::new("node 1.1"), 88 | Tree::new("node 1.2"), 89 | Tree::new("node 1.3").with_leaves([ 90 | Tree::new("node 1.3.1").with_glyphs(middle), 91 | Tree::new("node 1.3.2").with_glyphs(middle), 92 | Tree::new("node 1.3.3") 93 | .with_glyphs(middle) 94 | .with_leaves(["node 1.3.3.1", "node 1.3.3.2"]), 95 | ]), 96 | Tree::new("node 1.4").with_leaves([ 97 | Tree::new("node 1.4.1"), 98 | Tree::new("node 1.4.2"), 99 | Tree::new("node 1.4.3").with_leaves(["node 1.4.3.1", "node 1.4.3.2"]), 100 | ]), 101 | ]); 102 | assert_data_eq!( 103 | format!("{}", tree), 104 | str![[r#" 105 | node 1 106 | [mid ][indent ]node 1.1 107 | [mid ][indent ]node 1.2 108 | [mid ][indent ]node 1.3 109 | [mskip][iskip ](mid )(indent )node 1.3.1 110 | [mskip][iskip ](mid )(indent )node 1.3.2 111 | [mskip][iskip ](last )(indent )node 1.3.3 112 | [mskip][iskip ](lskip)(iskip )(mid )(indent )node 1.3.3.1 113 | [mskip][iskip ](lskip)(iskip )(last )(indent )node 1.3.3.2 114 | [last ][indent ]node 1.4 115 | [lskip][iskip ][mid ][indent ]node 1.4.1 116 | [lskip][iskip ][mid ][indent ]node 1.4.2 117 | [lskip][iskip ][last ][indent ]node 1.4.3 118 | [lskip][iskip ][lskip][iskip ][mid ][indent ]node 1.4.3.1 119 | [lskip][iskip ][lskip][iskip ][last ][indent ]node 1.4.3.2 120 | 121 | "#]] 122 | ); 123 | } 124 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to termtree 2 | 3 | Thanks for wanting to contribute! There are many ways to contribute and we 4 | appreciate any level you're willing to do. 5 | 6 | ## Feature Requests 7 | 8 | Need some new functionality to help? You can let us know by opening an 9 | [issue][new issue]. It's helpful to look through [all issues][all issues] in 10 | case it's already being talked about. 11 | 12 | ## Bug Reports 13 | 14 | Please let us know about what problems you run into, whether in behavior or 15 | ergonomics of API. You can do this by opening an [issue][new issue]. It's 16 | helpful to look through [all issues][all issues] in case it's already being 17 | talked about. 18 | 19 | ## Pull Requests 20 | 21 | Looking for an idea? Check our [issues][issues]. If the issue looks open ended, 22 | it is probably best to post on the issue how you are thinking of resolving the 23 | issue so you can get feedback early in the process. We want you to be 24 | successful and it can be discouraging to find out a lot of re-work is needed. 25 | 26 | Already have an idea? It might be good to first [create an issue][new issue] 27 | to propose it so we can make sure we are aligned and lower the risk of having 28 | to re-work some of it and the discouragement that goes along with that. 29 | 30 | ### Process 31 | 32 | As a heads up, we'll be running your PR through the following gauntlet: 33 | - warnings turned to compile errors 34 | - `cargo test` 35 | - `rustfmt` 36 | - `clippy` 37 | - `rustdoc` 38 | - [`committed`](https://github.com/crate-ci/committed) as we use [Conventional](https://www.conventionalcommits.org) commit style 39 | - [`typos`](https://github.com/crate-ci/typos) to check spelling 40 | 41 | Not everything can be checked automatically though. 42 | 43 | We request that the commit history gets cleaned up. 44 | 45 | We ask that commits are atomic, meaning they are complete and have a single responsibility. 46 | A complete commit should build, pass tests, update documentation and tests, and not have dead code. 47 | 48 | PRs should tell a cohesive story, with refactor and test commits that keep the 49 | fix or feature commits simple and clear. 50 | 51 | Specifically, we would encourage 52 | - File renames be isolated into their own commit 53 | - Add tests in a commit before their feature or fix, showing the current behavior (i.e. they should pass). 54 | The diff for the feature/fix commit will then show how the behavior changed, 55 | making the commit's intent clearer to reviewers and the community, and showing people that the 56 | test is verifying the expected state. 57 | - e.g. [clap#5520](https://github.com/clap-rs/clap/pull/5520) 58 | 59 | Note that we are talking about ideals. 60 | We understand having a clean history requires more advanced git skills; 61 | feel free to ask us for help! 62 | We might even suggest where it would work to be lax. 63 | We also understand that editing some early commits may cause a lot of churn 64 | with merge conflicts which can make it not worth editing all of the history. 65 | 66 | For code organization, we recommend 67 | - Grouping `impl` blocks next to their type (or trait) 68 | - Grouping private items after the `pub` item that uses them. 69 | - The intent is to help people quickly find the "relevant" details, allowing them to "dig deeper" as needed. Or put another way, the `pub` items serve as a table-of-contents. 70 | - The exact order is fuzzy; do what makes sense 71 | 72 | ## Releasing 73 | 74 | Pre-requisites 75 | - Running `cargo login` 76 | - A member of `rust-cli:Maintainers` 77 | - Push permission to the repo 78 | - [`cargo-release`](https://github.com/crate-ci/cargo-release/) 79 | 80 | When we're ready to release, a project owner should do the following 81 | 1. Update the changelog 82 | 2. Determine what the next version is, according to semver 83 | 3. Run [`cargo release -x `](https://github.com/crate-ci/cargo-release) 84 | 85 | [issues]: https://github.com/ORG/PROJECT/issues 86 | [new issue]: https://github.com/ORG/PROJECT/issues/new 87 | [all issues]: https://github.com/ORG/PROJECT/issues?utf8=%E2%9C%93&q=is%3Aissue 88 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | 4 | [workspace.package] 5 | repository = "https://github.com/rust-cli/termtree" 6 | license = "MIT" 7 | edition = "2021" 8 | rust-version = "1.74" # MSRV 9 | include = [ 10 | "build.rs", 11 | "src/**/*", 12 | "Cargo.toml", 13 | "Cargo.lock", 14 | "LICENSE*", 15 | "README.md", 16 | "examples/**/*" 17 | ] 18 | 19 | [workspace.lints.rust] 20 | rust_2018_idioms = { level = "warn", priority = -1 } 21 | unnameable_types = "warn" 22 | unreachable_pub = "warn" 23 | unsafe_op_in_unsafe_fn = "warn" 24 | unused_lifetimes = "warn" 25 | unused_macro_rules = "warn" 26 | unused_qualifications = "warn" 27 | 28 | [workspace.lints.clippy] 29 | bool_assert_comparison = "allow" 30 | branches_sharing_code = "allow" 31 | checked_conversions = "warn" 32 | collapsible_else_if = "allow" 33 | create_dir = "warn" 34 | dbg_macro = "warn" 35 | debug_assert_with_mut_call = "warn" 36 | doc_markdown = "warn" 37 | empty_enum = "warn" 38 | enum_glob_use = "warn" 39 | expl_impl_clone_on_copy = "warn" 40 | explicit_deref_methods = "warn" 41 | explicit_into_iter_loop = "warn" 42 | fallible_impl_from = "warn" 43 | filter_map_next = "warn" 44 | flat_map_option = "warn" 45 | float_cmp_const = "warn" 46 | fn_params_excessive_bools = "warn" 47 | from_iter_instead_of_collect = "warn" 48 | if_same_then_else = "allow" 49 | implicit_clone = "warn" 50 | imprecise_flops = "warn" 51 | inconsistent_struct_constructor = "warn" 52 | inefficient_to_string = "warn" 53 | infinite_loop = "warn" 54 | invalid_upcast_comparisons = "warn" 55 | large_digit_groups = "warn" 56 | large_stack_arrays = "warn" 57 | large_types_passed_by_value = "warn" 58 | let_and_return = "allow" # sometimes good to name what you are returning 59 | linkedlist = "warn" 60 | lossy_float_literal = "warn" 61 | macro_use_imports = "warn" 62 | mem_forget = "warn" 63 | mutex_integer = "warn" 64 | needless_continue = "allow" 65 | needless_for_each = "warn" 66 | negative_feature_names = "warn" 67 | path_buf_push_overwrite = "warn" 68 | ptr_as_ptr = "warn" 69 | rc_mutex = "warn" 70 | redundant_feature_names = "warn" 71 | ref_option_ref = "warn" 72 | rest_pat_in_fully_bound_structs = "warn" 73 | result_large_err = "allow" 74 | same_functions_in_if_condition = "warn" 75 | self_named_module_files = "warn" 76 | semicolon_if_nothing_returned = "warn" 77 | str_to_string = "warn" 78 | string_add = "warn" 79 | string_add_assign = "warn" 80 | string_lit_as_bytes = "warn" 81 | string_to_string = "warn" 82 | todo = "warn" 83 | trait_duplication_in_bounds = "warn" 84 | uninlined_format_args = "warn" 85 | verbose_file_reads = "warn" 86 | wildcard_imports = "warn" 87 | zero_sized_map_values = "warn" 88 | 89 | [profile.dev] 90 | panic = "abort" 91 | 92 | [profile.release] 93 | panic = "abort" 94 | codegen-units = 1 95 | lto = true 96 | # debug = "line-tables-only" # requires Cargo 1.71 97 | 98 | [package] 99 | name = "termtree" 100 | version = "0.5.1" 101 | description = "Visualize tree-like data on the command-line" 102 | documentation = "https://docs.rs/termtree" 103 | homepage = "https://github.com/rust-cli/termtree" 104 | categories = ["command-line-interface", "visualization"] 105 | keywords = ["cli", "tree", "dag"] 106 | repository.workspace = true 107 | license.workspace = true 108 | edition.workspace = true 109 | rust-version.workspace = true 110 | include.workspace = true 111 | 112 | [package.metadata.docs.rs] 113 | all-features = true 114 | rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"] 115 | 116 | [package.metadata.release] 117 | pre-release-replacements = [ 118 | {file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1}, 119 | {file="CHANGELOG.md", search="\\.\\.\\.HEAD", replace="...{{tag_name}}", exactly=1}, 120 | {file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1}, 121 | {file="CHANGELOG.md", search="", replace="\n## [Unreleased] - ReleaseDate\n", exactly=1}, 122 | {file="CHANGELOG.md", search="", replace="\n[Unreleased]: https://github.com/rust-cli/termtree/compare/{{tag_name}}...HEAD", exactly=1}, 123 | ] 124 | 125 | [dependencies] 126 | 127 | [dev-dependencies] 128 | snapbox = "0.6.10" 129 | 130 | [lints] 131 | workspace = true 132 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | pull_request: 8 | push: 9 | branches: 10 | - main 11 | 12 | env: 13 | RUST_BACKTRACE: 1 14 | CARGO_TERM_COLOR: always 15 | CLICOLOR: 1 16 | 17 | concurrency: 18 | group: "${{ github.workflow }}-${{ github.ref }}" 19 | cancel-in-progress: true 20 | 21 | jobs: 22 | ci: 23 | permissions: 24 | contents: none 25 | name: CI 26 | needs: [test, msrv, lockfile, docs, rustfmt, clippy, minimal-versions] 27 | runs-on: ubuntu-latest 28 | if: "always()" 29 | steps: 30 | - name: Failed 31 | run: exit 1 32 | if: "contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped')" 33 | test: 34 | name: Test 35 | strategy: 36 | matrix: 37 | os: ["ubuntu-latest", "windows-latest", "macos-latest"] 38 | rust: ["stable"] 39 | continue-on-error: ${{ matrix.rust != 'stable' }} 40 | runs-on: ${{ matrix.os }} 41 | steps: 42 | - name: Checkout repository 43 | uses: actions/checkout@v6 44 | - name: Install Rust 45 | uses: dtolnay/rust-toolchain@stable 46 | with: 47 | toolchain: ${{ matrix.rust }} 48 | - uses: Swatinem/rust-cache@v2 49 | - uses: taiki-e/install-action@cargo-hack 50 | - name: Build 51 | run: cargo test --workspace --no-run 52 | - name: Test 53 | run: cargo hack test --each-feature --workspace 54 | msrv: 55 | name: "Check MSRV" 56 | runs-on: ubuntu-latest 57 | steps: 58 | - name: Checkout repository 59 | uses: actions/checkout@v6 60 | - name: Install Rust 61 | uses: dtolnay/rust-toolchain@stable 62 | with: 63 | toolchain: stable 64 | - uses: Swatinem/rust-cache@v2 65 | - uses: taiki-e/install-action@cargo-hack 66 | - name: Default features 67 | run: cargo hack check --each-feature --locked --rust-version --ignore-private --workspace --all-targets --keep-going 68 | minimal-versions: 69 | name: Minimal versions 70 | runs-on: ubuntu-latest 71 | steps: 72 | - name: Checkout repository 73 | uses: actions/checkout@v6 74 | - name: Install stable Rust 75 | uses: dtolnay/rust-toolchain@stable 76 | with: 77 | toolchain: stable 78 | - name: Install nightly Rust 79 | uses: dtolnay/rust-toolchain@stable 80 | with: 81 | toolchain: nightly 82 | - name: Downgrade dependencies to minimal versions 83 | run: cargo +nightly generate-lockfile -Z minimal-versions 84 | - name: Compile with minimal versions 85 | run: cargo +stable check --workspace --all-features --locked --keep-going 86 | lockfile: 87 | runs-on: ubuntu-latest 88 | steps: 89 | - name: Checkout repository 90 | uses: actions/checkout@v6 91 | - name: Install Rust 92 | uses: dtolnay/rust-toolchain@stable 93 | with: 94 | toolchain: stable 95 | - uses: Swatinem/rust-cache@v2 96 | - name: "Is lockfile updated?" 97 | run: cargo update --workspace --locked 98 | docs: 99 | name: Docs 100 | runs-on: ubuntu-latest 101 | steps: 102 | - name: Checkout repository 103 | uses: actions/checkout@v6 104 | - name: Install Rust 105 | uses: dtolnay/rust-toolchain@stable 106 | with: 107 | toolchain: "1.92" # STABLE 108 | - uses: Swatinem/rust-cache@v2 109 | - name: Check documentation 110 | env: 111 | RUSTDOCFLAGS: -D warnings 112 | run: cargo doc --workspace --all-features --no-deps --document-private-items --keep-going 113 | rustfmt: 114 | name: rustfmt 115 | runs-on: ubuntu-latest 116 | steps: 117 | - name: Checkout repository 118 | uses: actions/checkout@v6 119 | - name: Install Rust 120 | uses: dtolnay/rust-toolchain@stable 121 | with: 122 | toolchain: "1.92" # STABLE 123 | components: rustfmt 124 | - uses: Swatinem/rust-cache@v2 125 | - name: Check formatting 126 | run: cargo fmt --all -- --check 127 | clippy: 128 | name: clippy 129 | runs-on: ubuntu-latest 130 | permissions: 131 | security-events: write # to upload sarif results 132 | steps: 133 | - name: Checkout repository 134 | uses: actions/checkout@v6 135 | - name: Install Rust 136 | uses: dtolnay/rust-toolchain@stable 137 | with: 138 | toolchain: "1.92" # STABLE 139 | components: clippy 140 | - uses: Swatinem/rust-cache@v2 141 | - name: Install SARIF tools 142 | run: cargo install clippy-sarif --locked 143 | - name: Install SARIF tools 144 | run: cargo install sarif-fmt --locked 145 | - name: Check 146 | run: > 147 | cargo clippy --workspace --all-features --all-targets --message-format=json 148 | | clippy-sarif 149 | | tee clippy-results.sarif 150 | | sarif-fmt 151 | continue-on-error: true 152 | - name: Upload 153 | uses: github/codeql-action/upload-sarif@v4 154 | with: 155 | sarif_file: clippy-results.sarif 156 | wait-for-processing: true 157 | - name: Report status 158 | run: cargo clippy --workspace --all-features --all-targets --keep-going -- -D warnings --allow deprecated 159 | coverage: 160 | name: Coverage 161 | runs-on: ubuntu-latest 162 | steps: 163 | - name: Checkout repository 164 | uses: actions/checkout@v6 165 | - name: Install Rust 166 | uses: dtolnay/rust-toolchain@stable 167 | with: 168 | toolchain: stable 169 | - uses: Swatinem/rust-cache@v2 170 | - name: Install cargo-tarpaulin 171 | run: cargo install cargo-tarpaulin 172 | - name: Gather coverage 173 | run: cargo tarpaulin --output-dir coverage --out lcov 174 | - name: Publish to Coveralls 175 | uses: coverallsapp/github-action@master 176 | with: 177 | github-token: ${{ secrets.GITHUB_TOKEN }} 178 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 2 | #![warn(clippy::print_stderr)] 3 | #![warn(clippy::print_stdout)] 4 | 5 | #[cfg(test)] 6 | mod tests; 7 | 8 | use std::collections::VecDeque; 9 | use std::fmt::{self, Display}; 10 | use std::rc::Rc; 11 | 12 | /// a simple recursive type which is able to render its 13 | /// components in a tree-like format 14 | #[derive(Debug, Clone)] 15 | pub struct Tree { 16 | pub root: D, 17 | pub leaves: Vec>, 18 | multiline: bool, 19 | glyphs: Option, 20 | } 21 | 22 | impl Tree { 23 | pub fn new(root: D) -> Self { 24 | Tree { 25 | root, 26 | leaves: Vec::new(), 27 | multiline: false, 28 | glyphs: None, 29 | } 30 | } 31 | 32 | pub fn with_leaves(mut self, leaves: impl IntoIterator>>) -> Self { 33 | self.leaves = leaves.into_iter().map(Into::into).collect(); 34 | self 35 | } 36 | 37 | /// Ensure all lines for `root` are indented 38 | pub fn with_multiline(mut self, yes: bool) -> Self { 39 | self.multiline = yes; 40 | self 41 | } 42 | 43 | /// Customize the rendering of this node 44 | pub fn with_glyphs(mut self, glyphs: GlyphPalette) -> Self { 45 | self.glyphs = Some(glyphs); 46 | self 47 | } 48 | } 49 | 50 | impl Tree { 51 | /// Ensure all lines for `root` are indented 52 | pub fn set_multiline(&mut self, yes: bool) -> &mut Self { 53 | self.multiline = yes; 54 | self 55 | } 56 | 57 | /// Customize the rendering of this node 58 | pub fn set_glyphs(&mut self, glyphs: GlyphPalette) -> &mut Self { 59 | self.glyphs = Some(glyphs); 60 | self 61 | } 62 | } 63 | 64 | impl Tree { 65 | pub fn push(&mut self, leaf: impl Into>) -> &mut Self { 66 | self.leaves.push(leaf.into()); 67 | self 68 | } 69 | } 70 | 71 | impl From for Tree { 72 | fn from(inner: D) -> Self { 73 | Self::new(inner) 74 | } 75 | } 76 | 77 | impl Extend for Tree { 78 | fn extend>(&mut self, iter: T) { 79 | self.leaves.extend(iter.into_iter().map(Into::into)); 80 | } 81 | } 82 | 83 | impl Extend> for Tree { 84 | fn extend>>(&mut self, iter: T) { 85 | self.leaves.extend(iter); 86 | } 87 | } 88 | 89 | impl Display for Tree { 90 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 91 | self.root.fmt(f)?; // Pass along `f.alternate()` 92 | writeln!(f)?; 93 | let mut queue = DisplauQueue::new(); 94 | let no_space = Rc::new(Vec::new()); 95 | let default_glyphs = GlyphPalette::new(); 96 | let glyphs = self.glyphs.as_ref().unwrap_or(&default_glyphs); 97 | enqueue_leaves(&mut queue, self, glyphs, no_space); 98 | while let Some((last, leaf, glyphs, spaces)) = queue.pop_front() { 99 | let mut prefix = ( 100 | if last { 101 | glyphs.last_item 102 | } else { 103 | glyphs.middle_item 104 | }, 105 | glyphs.item_indent, 106 | ); 107 | 108 | if leaf.multiline { 109 | let rest_prefix = ( 110 | if last { 111 | glyphs.last_skip 112 | } else { 113 | glyphs.middle_skip 114 | }, 115 | glyphs.skip_indent, 116 | ); 117 | debug_assert_eq!(prefix.0.chars().count(), rest_prefix.0.chars().count()); 118 | debug_assert_eq!(prefix.1.chars().count(), rest_prefix.1.chars().count()); 119 | 120 | let root = if f.alternate() { 121 | format!("{:#}", leaf.root) 122 | } else { 123 | format!("{:}", leaf.root) 124 | }; 125 | for line in root.lines() { 126 | // print single line 127 | for s in spaces.as_slice() { 128 | s.skip.fmt(f)?; 129 | s.indent.fmt(f)?; 130 | } 131 | prefix.0.fmt(f)?; 132 | prefix.1.fmt(f)?; 133 | line.fmt(f)?; 134 | writeln!(f)?; 135 | prefix = rest_prefix; 136 | } 137 | } else { 138 | // print single line 139 | for s in spaces.as_slice() { 140 | s.skip.fmt(f)?; 141 | s.indent.fmt(f)?; 142 | } 143 | prefix.0.fmt(f)?; 144 | prefix.1.fmt(f)?; 145 | leaf.root.fmt(f)?; // Pass along `f.alternate()` 146 | writeln!(f)?; 147 | } 148 | 149 | // recurse 150 | if !leaf.leaves.is_empty() { 151 | let s: &Vec = &spaces; 152 | let mut child_spaces = s.clone(); 153 | child_spaces.push(if last { 154 | glyphs.last_space() 155 | } else { 156 | glyphs.middle_space() 157 | }); 158 | let child_spaces = Rc::new(child_spaces); 159 | enqueue_leaves(&mut queue, leaf, glyphs, child_spaces); 160 | } 161 | } 162 | Ok(()) 163 | } 164 | } 165 | 166 | type DisplauQueue<'t, D> = VecDeque<(bool, &'t Tree, &'t GlyphPalette, Rc>)>; 167 | 168 | fn enqueue_leaves<'t, D: Display>( 169 | queue: &mut DisplauQueue<'t, D>, 170 | parent: &'t Tree, 171 | parent_glyphs: &'t GlyphPalette, 172 | spaces: Rc>, 173 | ) { 174 | for (i, leaf) in parent.leaves.iter().rev().enumerate() { 175 | let last = i == 0; 176 | let glyphs = leaf.glyphs.as_ref().unwrap_or(parent_glyphs); 177 | queue.push_front((last, leaf, glyphs, spaces.clone())); 178 | } 179 | } 180 | 181 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 182 | struct SpacePalette { 183 | skip: &'static str, 184 | indent: &'static str, 185 | } 186 | 187 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 188 | pub struct GlyphPalette { 189 | pub middle_item: &'static str, 190 | pub last_item: &'static str, 191 | pub item_indent: &'static str, 192 | 193 | pub middle_skip: &'static str, 194 | pub last_skip: &'static str, 195 | pub skip_indent: &'static str, 196 | } 197 | 198 | impl GlyphPalette { 199 | pub const fn new() -> Self { 200 | Self { 201 | middle_item: "├", 202 | last_item: "└", 203 | item_indent: "── ", 204 | 205 | middle_skip: "│", 206 | last_skip: " ", 207 | skip_indent: " ", 208 | } 209 | } 210 | 211 | fn middle_space(&self) -> SpacePalette { 212 | SpacePalette { 213 | skip: self.middle_skip, 214 | indent: self.skip_indent, 215 | } 216 | } 217 | 218 | fn last_space(&self) -> SpacePalette { 219 | SpacePalette { 220 | skip: self.last_skip, 221 | indent: self.skip_indent, 222 | } 223 | } 224 | } 225 | 226 | impl Default for GlyphPalette { 227 | fn default() -> Self { 228 | Self::new() 229 | } 230 | } 231 | 232 | #[doc = include_str!("../README.md")] 233 | #[cfg(doctest)] 234 | pub struct ReadmeDoctests; 235 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anstream" 7 | version = "0.6.21" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" 10 | dependencies = [ 11 | "anstyle", 12 | "anstyle-parse", 13 | "anstyle-query", 14 | "anstyle-wincon", 15 | "colorchoice", 16 | "is_terminal_polyfill", 17 | "utf8parse", 18 | ] 19 | 20 | [[package]] 21 | name = "anstyle" 22 | version = "1.0.13" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" 25 | 26 | [[package]] 27 | name = "anstyle-parse" 28 | version = "0.2.4" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" 31 | dependencies = [ 32 | "utf8parse", 33 | ] 34 | 35 | [[package]] 36 | name = "anstyle-query" 37 | version = "1.1.0" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" 40 | dependencies = [ 41 | "windows-sys 0.52.0", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-wincon" 46 | version = "3.0.10" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" 49 | dependencies = [ 50 | "anstyle", 51 | "once_cell_polyfill", 52 | "windows-sys 0.60.2", 53 | ] 54 | 55 | [[package]] 56 | name = "colorchoice" 57 | version = "1.0.1" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" 60 | 61 | [[package]] 62 | name = "is_terminal_polyfill" 63 | version = "1.70.0" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" 66 | 67 | [[package]] 68 | name = "normalize-line-endings" 69 | version = "0.3.0" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" 72 | 73 | [[package]] 74 | name = "once_cell_polyfill" 75 | version = "1.70.2" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" 78 | 79 | [[package]] 80 | name = "similar" 81 | version = "2.7.0" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" 84 | 85 | [[package]] 86 | name = "snapbox" 87 | version = "0.6.23" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "96fa1ce81be900d083b30ec2d481e6658c2acfaa2cfc7be45ccc2cc1b820edb3" 90 | dependencies = [ 91 | "anstream", 92 | "anstyle", 93 | "normalize-line-endings", 94 | "similar", 95 | "snapbox-macros", 96 | ] 97 | 98 | [[package]] 99 | name = "snapbox-macros" 100 | version = "0.4.0" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "3b750c344002d7cc69afb9da00ebd9b5c0f8ac2eb7d115d9d45d5b5f47718d74" 103 | dependencies = [ 104 | "anstream", 105 | ] 106 | 107 | [[package]] 108 | name = "termtree" 109 | version = "0.5.1" 110 | dependencies = [ 111 | "snapbox", 112 | ] 113 | 114 | [[package]] 115 | name = "utf8parse" 116 | version = "0.2.2" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 119 | 120 | [[package]] 121 | name = "windows-link" 122 | version = "0.2.1" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 125 | 126 | [[package]] 127 | name = "windows-sys" 128 | version = "0.52.0" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 131 | dependencies = [ 132 | "windows-targets 0.52.6", 133 | ] 134 | 135 | [[package]] 136 | name = "windows-sys" 137 | version = "0.60.2" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" 140 | dependencies = [ 141 | "windows-targets 0.53.5", 142 | ] 143 | 144 | [[package]] 145 | name = "windows-targets" 146 | version = "0.52.6" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 149 | dependencies = [ 150 | "windows_aarch64_gnullvm 0.52.6", 151 | "windows_aarch64_msvc 0.52.6", 152 | "windows_i686_gnu 0.52.6", 153 | "windows_i686_gnullvm 0.52.6", 154 | "windows_i686_msvc 0.52.6", 155 | "windows_x86_64_gnu 0.52.6", 156 | "windows_x86_64_gnullvm 0.52.6", 157 | "windows_x86_64_msvc 0.52.6", 158 | ] 159 | 160 | [[package]] 161 | name = "windows-targets" 162 | version = "0.53.5" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" 165 | dependencies = [ 166 | "windows-link", 167 | "windows_aarch64_gnullvm 0.53.1", 168 | "windows_aarch64_msvc 0.53.1", 169 | "windows_i686_gnu 0.53.1", 170 | "windows_i686_gnullvm 0.53.1", 171 | "windows_i686_msvc 0.53.1", 172 | "windows_x86_64_gnu 0.53.1", 173 | "windows_x86_64_gnullvm 0.53.1", 174 | "windows_x86_64_msvc 0.53.1", 175 | ] 176 | 177 | [[package]] 178 | name = "windows_aarch64_gnullvm" 179 | version = "0.52.6" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 182 | 183 | [[package]] 184 | name = "windows_aarch64_gnullvm" 185 | version = "0.53.1" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" 188 | 189 | [[package]] 190 | name = "windows_aarch64_msvc" 191 | version = "0.52.6" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 194 | 195 | [[package]] 196 | name = "windows_aarch64_msvc" 197 | version = "0.53.1" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" 200 | 201 | [[package]] 202 | name = "windows_i686_gnu" 203 | version = "0.52.6" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 206 | 207 | [[package]] 208 | name = "windows_i686_gnu" 209 | version = "0.53.1" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" 212 | 213 | [[package]] 214 | name = "windows_i686_gnullvm" 215 | version = "0.52.6" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 218 | 219 | [[package]] 220 | name = "windows_i686_gnullvm" 221 | version = "0.53.1" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" 224 | 225 | [[package]] 226 | name = "windows_i686_msvc" 227 | version = "0.52.6" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 230 | 231 | [[package]] 232 | name = "windows_i686_msvc" 233 | version = "0.53.1" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" 236 | 237 | [[package]] 238 | name = "windows_x86_64_gnu" 239 | version = "0.52.6" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 242 | 243 | [[package]] 244 | name = "windows_x86_64_gnu" 245 | version = "0.53.1" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" 248 | 249 | [[package]] 250 | name = "windows_x86_64_gnullvm" 251 | version = "0.52.6" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 254 | 255 | [[package]] 256 | name = "windows_x86_64_gnullvm" 257 | version = "0.53.1" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" 260 | 261 | [[package]] 262 | name = "windows_x86_64_msvc" 263 | version = "0.52.6" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 266 | 267 | [[package]] 268 | name = "windows_x86_64_msvc" 269 | version = "0.53.1" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" 272 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | # Note that all fields that take a lint level have these possible values: 2 | # * deny - An error will be produced and the check will fail 3 | # * warn - A warning will be produced, but the check will not fail 4 | # * allow - No warning or error will be produced, though in some cases a note 5 | # will be 6 | 7 | # Root options 8 | 9 | # The graph table configures how the dependency graph is constructed and thus 10 | # which crates the checks are performed against 11 | [graph] 12 | # If 1 or more target triples (and optionally, target_features) are specified, 13 | # only the specified targets will be checked when running `cargo deny check`. 14 | # This means, if a particular package is only ever used as a target specific 15 | # dependency, such as, for example, the `nix` crate only being used via the 16 | # `target_family = "unix"` configuration, that only having windows targets in 17 | # this list would mean the nix crate, as well as any of its exclusive 18 | # dependencies not shared by any other crates, would be ignored, as the target 19 | # list here is effectively saying which targets you are building for. 20 | targets = [ 21 | # The triple can be any string, but only the target triples built in to 22 | # rustc (as of 1.40) can be checked against actual config expressions 23 | #"x86_64-unknown-linux-musl", 24 | # You can also specify which target_features you promise are enabled for a 25 | # particular target. target_features are currently not validated against 26 | # the actual valid features supported by the target architecture. 27 | #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, 28 | ] 29 | # When creating the dependency graph used as the source of truth when checks are 30 | # executed, this field can be used to prune crates from the graph, removing them 31 | # from the view of cargo-deny. This is an extremely heavy hammer, as if a crate 32 | # is pruned from the graph, all of its dependencies will also be pruned unless 33 | # they are connected to another crate in the graph that hasn't been pruned, 34 | # so it should be used with care. The identifiers are [Package ID Specifications] 35 | # (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html) 36 | #exclude = [] 37 | # If true, metadata will be collected with `--all-features`. Note that this can't 38 | # be toggled off if true, if you want to conditionally enable `--all-features` it 39 | # is recommended to pass `--all-features` on the cmd line instead 40 | all-features = false 41 | # If true, metadata will be collected with `--no-default-features`. The same 42 | # caveat with `all-features` applies 43 | no-default-features = false 44 | # If set, these feature will be enabled when collecting metadata. If `--features` 45 | # is specified on the cmd line they will take precedence over this option. 46 | #features = [] 47 | 48 | # The output table provides options for how/if diagnostics are outputted 49 | [output] 50 | # When outputting inclusion graphs in diagnostics that include features, this 51 | # option can be used to specify the depth at which feature edges will be added. 52 | # This option is included since the graphs can be quite large and the addition 53 | # of features from the crate(s) to all of the graph roots can be far too verbose. 54 | # This option can be overridden via `--feature-depth` on the cmd line 55 | feature-depth = 1 56 | 57 | # This section is considered when running `cargo deny check advisories` 58 | # More documentation for the advisories section can be found here: 59 | # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html 60 | [advisories] 61 | # The path where the advisory databases are cloned/fetched into 62 | #db-path = "$CARGO_HOME/advisory-dbs" 63 | # The url(s) of the advisory databases to use 64 | #db-urls = ["https://github.com/rustsec/advisory-db"] 65 | # A list of advisory IDs to ignore. Note that ignored advisories will still 66 | # output a note when they are encountered. 67 | ignore = [ 68 | #"RUSTSEC-0000-0000", 69 | #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, 70 | #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish 71 | #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" }, 72 | ] 73 | # If this is true, then cargo deny will use the git executable to fetch advisory database. 74 | # If this is false, then it uses a built-in git library. 75 | # Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. 76 | # See Git Authentication for more information about setting up git authentication. 77 | #git-fetch-with-cli = true 78 | 79 | # This section is considered when running `cargo deny check licenses` 80 | # More documentation for the licenses section can be found here: 81 | # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html 82 | [licenses] 83 | # List of explicitly allowed licenses 84 | # See https://spdx.org/licenses/ for list of possible licenses 85 | # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. 86 | allow = [ 87 | "MIT", 88 | "MIT-0", 89 | "Apache-2.0", 90 | "BSD-2-Clause", 91 | "BSD-3-Clause", 92 | "MPL-2.0", 93 | "Unicode-DFS-2016", 94 | "CC0-1.0", 95 | "ISC", 96 | "OpenSSL", 97 | "Zlib", 98 | ] 99 | # The confidence threshold for detecting a license from license text. 100 | # The higher the value, the more closely the license text must be to the 101 | # canonical license text of a valid SPDX license file. 102 | # [possible values: any between 0.0 and 1.0]. 103 | confidence-threshold = 0.8 104 | # Allow 1 or more licenses on a per-crate basis, so that particular licenses 105 | # aren't accepted for every possible crate as with the normal allow list 106 | exceptions = [ 107 | # Each entry is the crate and version constraint, and its specific allow 108 | # list 109 | #{ allow = ["Zlib"], crate = "adler32" }, 110 | ] 111 | 112 | # Some crates don't have (easily) machine readable licensing information, 113 | # adding a clarification entry for it allows you to manually specify the 114 | # licensing information 115 | [[licenses.clarify]] 116 | # The package spec the clarification applies to 117 | crate = "ring" 118 | # The SPDX expression for the license requirements of the crate 119 | expression = "MIT AND ISC AND OpenSSL" 120 | # One or more files in the crate's source used as the "source of truth" for 121 | # the license expression. If the contents match, the clarification will be used 122 | # when running the license check, otherwise the clarification will be ignored 123 | # and the crate will be checked normally, which may produce warnings or errors 124 | # depending on the rest of your configuration 125 | license-files = [ 126 | # Each entry is a crate relative path, and the (opaque) hash of its contents 127 | { path = "LICENSE", hash = 0xbd0eed23 } 128 | ] 129 | 130 | [licenses.private] 131 | # If true, ignores workspace crates that aren't published, or are only 132 | # published to private registries. 133 | # To see how to mark a crate as unpublished (to the official registry), 134 | # visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. 135 | ignore = true 136 | # One or more private registries that you might publish crates to, if a crate 137 | # is only published to private registries, and ignore is true, the crate will 138 | # not have its license(s) checked 139 | registries = [ 140 | #"https://sekretz.com/registry 141 | ] 142 | 143 | # This section is considered when running `cargo deny check bans`. 144 | # More documentation about the 'bans' section can be found here: 145 | # https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html 146 | [bans] 147 | # Lint level for when multiple versions of the same crate are detected 148 | multiple-versions = "warn" 149 | # Lint level for when a crate version requirement is `*` 150 | wildcards = "allow" 151 | # The graph highlighting used when creating dotgraphs for crates 152 | # with multiple versions 153 | # * lowest-version - The path to the lowest versioned duplicate is highlighted 154 | # * simplest-path - The path to the version with the fewest edges is highlighted 155 | # * all - Both lowest-version and simplest-path are used 156 | highlight = "all" 157 | # The default lint level for `default` features for crates that are members of 158 | # the workspace that is being checked. This can be overridden by allowing/denying 159 | # `default` on a crate-by-crate basis if desired. 160 | workspace-default-features = "allow" 161 | # The default lint level for `default` features for external crates that are not 162 | # members of the workspace. This can be overridden by allowing/denying `default` 163 | # on a crate-by-crate basis if desired. 164 | external-default-features = "allow" 165 | # List of crates that are allowed. Use with care! 166 | allow = [ 167 | #"ansi_term@0.11.0", 168 | #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" }, 169 | ] 170 | # List of crates to deny 171 | deny = [ 172 | #"ansi_term@0.11.0", 173 | #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" }, 174 | # Wrapper crates can optionally be specified to allow the crate when it 175 | # is a direct dependency of the otherwise banned crate 176 | #{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] }, 177 | ] 178 | 179 | # List of features to allow/deny 180 | # Each entry the name of a crate and a version range. If version is 181 | # not specified, all versions will be matched. 182 | #[[bans.features]] 183 | #crate = "reqwest" 184 | # Features to not allow 185 | #deny = ["json"] 186 | # Features to allow 187 | #allow = [ 188 | # "rustls", 189 | # "__rustls", 190 | # "__tls", 191 | # "hyper-rustls", 192 | # "rustls", 193 | # "rustls-pemfile", 194 | # "rustls-tls-webpki-roots", 195 | # "tokio-rustls", 196 | # "webpki-roots", 197 | #] 198 | # If true, the allowed features must exactly match the enabled feature set. If 199 | # this is set there is no point setting `deny` 200 | #exact = true 201 | 202 | # Certain crates/versions that will be skipped when doing duplicate detection. 203 | skip = [ 204 | #"ansi_term@0.11.0", 205 | #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" }, 206 | ] 207 | # Similarly to `skip` allows you to skip certain crates during duplicate 208 | # detection. Unlike skip, it also includes the entire tree of transitive 209 | # dependencies starting at the specified crate, up to a certain depth, which is 210 | # by default infinite. 211 | skip-tree = [ 212 | #"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies 213 | #{ crate = "ansi_term@0.11.0", depth = 20 }, 214 | ] 215 | 216 | # This section is considered when running `cargo deny check sources`. 217 | # More documentation about the 'sources' section can be found here: 218 | # https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html 219 | [sources] 220 | # Lint level for what to happen when a crate from a crate registry that is not 221 | # in the allow list is encountered 222 | unknown-registry = "deny" 223 | # Lint level for what to happen when a crate from a git repository that is not 224 | # in the allow list is encountered 225 | unknown-git = "deny" 226 | # List of URLs for allowed crate registries. Defaults to the crates.io index 227 | # if not specified. If it is specified but empty, no registries are allowed. 228 | allow-registry = ["https://github.com/rust-lang/crates.io-index"] 229 | # List of URLs for allowed Git repositories 230 | allow-git = [] 231 | 232 | [sources.allow-org] 233 | # 1 or more github.com organizations to allow git sources for 234 | github = [] 235 | # 1 or more gitlab.com organizations to allow git sources for 236 | gitlab = [] 237 | # 1 or more bitbucket.org organizations to allow git sources for 238 | bitbucket = [] 239 | --------------------------------------------------------------------------------