├── src ├── tests │ ├── helpers │ │ ├── mod.rs │ │ └── test_run.rs │ ├── mod.rs │ ├── wipe_permissions.rs │ └── wipe.rs ├── main.rs ├── wipe_params.rs ├── wipe.rs ├── dir_helpers.rs ├── command.rs └── writer.rs ├── .gitignore ├── assets └── screenshot.PNG ├── .github ├── workflows │ ├── publish.yml │ ├── security-audit.yml │ └── ci.yml └── dependabot.yml ├── Makefile.toml ├── Cargo.toml ├── LICENSE ├── README.md ├── CHANGELOG.md └── Cargo.lock /src/tests/helpers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod test_run; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | cobertura.xml 3 | .vscode 4 | -------------------------------------------------------------------------------- /src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod helpers; 2 | mod wipe; 3 | mod wipe_permissions; 4 | -------------------------------------------------------------------------------- /assets/screenshot.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihai-dinculescu/cargo-wipe/HEAD/assets/screenshot.PNG -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Crates.io 2 | on: 3 | release: 4 | types: 5 | - created 6 | jobs: 7 | publish: 8 | name: Publish 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: dtolnay/rust-toolchain@stable 12 | - uses: actions/checkout@v5 13 | with: 14 | ref: main 15 | - name: Run cargo login 16 | run: cargo login ${CRATES_IO_TOKEN} 17 | env: 18 | CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} 19 | - name: Run build 20 | run: cargo build --release --verbose 21 | - name: Run cargo publish 22 | run: cargo publish 23 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::stdout; 2 | 3 | use clap::Parser; 4 | 5 | pub mod command; 6 | pub mod dir_helpers; 7 | pub mod wipe; 8 | pub mod wipe_params; 9 | pub mod writer; 10 | 11 | use crate::command::Command; 12 | use crate::wipe::Wipe; 13 | use crate::wipe_params::WipeParams; 14 | 15 | #[cfg(test)] 16 | mod tests; 17 | 18 | fn main() -> anyhow::Result<()> { 19 | let mut stdout = stdout(); 20 | let command = Command::parse(); 21 | 22 | match command { 23 | Command::Wipe(args) => { 24 | let params = WipeParams::new(&args)?; 25 | Wipe::new(&mut stdout, ¶ms).run()?; 26 | } 27 | } 28 | 29 | Ok(()) 30 | } 31 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Please see the documentation for all configuration options: 2 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "cargo" 7 | directory: "/" 8 | schedule: 9 | interval: "daily" 10 | commit-message: 11 | prefix: "chore" 12 | prefix-development: "chore" 13 | include: "scope" 14 | - package-ecosystem: "github-actions" 15 | directory: "/" 16 | schedule: 17 | interval: "daily" 18 | commit-message: 19 | prefix: "chore" 20 | prefix-development: "chore" 21 | include: "scope" 22 | -------------------------------------------------------------------------------- /.github/workflows/security-audit.yml: -------------------------------------------------------------------------------- 1 | name: Security 2 | on: 3 | push: 4 | branches: 5 | - main 6 | paths: 7 | - "**/Cargo.toml" 8 | - "**/Cargo.lock" 9 | pull_request: 10 | branches: 11 | - main 12 | paths: 13 | - "**/Cargo.toml" 14 | - "**/Cargo.lock" 15 | schedule: 16 | - cron: "0 0 * * *" 17 | jobs: 18 | security_audit: 19 | name: Audit 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: dtolnay/rust-toolchain@stable 23 | - uses: actions/checkout@v5 24 | - uses: actions-rs/audit-check@v1 25 | with: 26 | token: ${{ secrets.GITHUB_TOKEN }} 27 | -------------------------------------------------------------------------------- /Makefile.toml: -------------------------------------------------------------------------------- 1 | [config] 2 | skip_core_tasks = true 3 | 4 | [tasks.format] 5 | command = "cargo" 6 | args = ["fmt", "--verbose", "--", "--check"] 7 | 8 | [tasks.check] 9 | command = "cargo" 10 | args = ["check", "--verbose"] 11 | 12 | [tasks.clippy] 13 | command = "cargo" 14 | args = ["clippy", "--all-targets", "--all-features", "--verbose", "--", "-D", "warnings"] 15 | 16 | [tasks.test] 17 | command = "cargo" 18 | args = ["test", "--verbose"] 19 | 20 | [tasks.ci-flow] 21 | dependencies = [ 22 | "format", 23 | "check", 24 | "clippy", 25 | "test" 26 | ] 27 | 28 | [tasks.coverage] 29 | command = "cargo" 30 | args = ["tarpaulin", "--verbose", "--all-features", "--ignore-tests", "--timeout", "120", "--out", "Xml"] 31 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cargo-wipe" 3 | version = "0.4.0" 4 | edition = "2024" 5 | license = "MIT" 6 | authors = ["Mihai Dinculescu "] 7 | description = "Cargo subcommand that recursively finds and optionally wipes all \"target\" (Rust), \"node_modules\" (Node), or \".terraform\" (Terraform) folders that are found in the current path." 8 | keywords = ["cli", "cargo", "wipe", "target", "node_modules"] 9 | categories = ["command-line-interface", "command-line-utilities"] 10 | readme = "README.md" 11 | repository = "https://github.com/mihai-dinculescu/cargo-wipe" 12 | 13 | [dependencies] 14 | anyhow = "1.0" 15 | num-format = "0.4" 16 | number_prefix = "0.4" 17 | clap = { version = "4.5", features = ["color", "derive"] } 18 | yansi = "1.0" 19 | 20 | [dev-dependencies] 21 | rand = "0.9" 22 | rand_distr = "0.5" 23 | rstest = "0.26" 24 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | checks: 13 | name: Rust checks 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: dtolnay/rust-toolchain@stable 17 | - uses: davidB/rust-cargo-make@v1 18 | - uses: actions/checkout@v5 19 | - name: Run format 20 | run: cargo make format 21 | - name: Run check 22 | run: cargo make check 23 | - name: Run clippy 24 | run: cargo make clippy 25 | - name: Run test 26 | run: cargo make test 27 | - name: Generate code coverage 28 | run: cargo make coverage 29 | - name: Upload to codecov.io 30 | uses: codecov/codecov-action@v5 31 | with: 32 | fail_ci_if_error: true 33 | token: ${{ secrets.CODECOV_TOKEN }} 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2025 Mihai Dinculescu. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/tests/wipe_permissions.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_os = "linux")] 2 | mod wipe_permissions_tests { 3 | use std::io::Cursor; 4 | use std::path::PathBuf; 5 | 6 | use rstest::rstest; 7 | 8 | use crate::command::LanguageEnum; 9 | use crate::tests::helpers::test_run::TestRun; 10 | use crate::wipe::Wipe; 11 | use crate::wipe_params::WipeParams; 12 | 13 | #[rstest] 14 | #[case(LanguageEnum::Node, false)] 15 | #[case(LanguageEnum::Node, true)] 16 | #[case(LanguageEnum::Rust, false)] 17 | #[case(LanguageEnum::Rust, true)] 18 | #[case(LanguageEnum::Terraform, false)] 19 | #[case(LanguageEnum::Terraform, true)] 20 | fn test_with_readonly_folders(#[case] language: LanguageEnum, #[case] wipe: bool) { 21 | use std::fs; 22 | use std::os::unix::fs::PermissionsExt; 23 | 24 | let test_run = TestRun::new(&language, 3, 0); 25 | 26 | let params = WipeParams { 27 | wipe, 28 | path: PathBuf::from(&test_run), 29 | language, 30 | ignores: Vec::new(), 31 | }; 32 | 33 | let first_hit = test_run.hits.first().unwrap().clone(); 34 | let first_hit_parent = first_hit.parent().unwrap(); 35 | 36 | let permissions = fs::Permissions::from_mode(0o555); 37 | fs::set_permissions(first_hit_parent, permissions).unwrap(); 38 | 39 | let mut buff = Cursor::new(Vec::new()); 40 | Wipe::new(&mut buff, ¶ms).run().unwrap(); 41 | 42 | let output = std::str::from_utf8(buff.get_ref()).unwrap(); 43 | println!("{output}"); 44 | 45 | // hits should be listed 46 | // and wiped if wipe is true and delete permissions are present 47 | for (i, path) in test_run.hits.iter().enumerate() { 48 | let expected = String::from(path.to_str().unwrap()); 49 | 50 | assert!(output.contains(&expected)); 51 | assert_eq!(path.exists(), !wipe || i == 0); 52 | } 53 | 54 | // revert the permissions change for cleanup 55 | let permissions = fs::Permissions::from_mode(0o777); 56 | fs::set_permissions(first_hit_parent, permissions).unwrap(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/wipe_params.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use std::{env, io}; 3 | 4 | use crate::command::{Args, LanguageEnum}; 5 | 6 | #[derive(Debug, PartialEq, Eq)] 7 | pub struct WipeParams { 8 | pub wipe: bool, 9 | pub path: PathBuf, 10 | pub language: LanguageEnum, 11 | pub ignores: Vec, 12 | } 13 | 14 | impl WipeParams { 15 | pub fn new(args: &Args) -> io::Result { 16 | let path = env::current_dir()?; 17 | 18 | Ok(Self { 19 | wipe: args.wipe, 20 | path, 21 | language: args.language.clone(), 22 | ignores: args.ignores.clone(), 23 | }) 24 | } 25 | } 26 | 27 | #[cfg(test)] 28 | mod tests { 29 | use std::path::PathBuf; 30 | 31 | use rstest::rstest; 32 | 33 | use crate::command::{Args, LanguageEnum}; 34 | use crate::wipe_params::WipeParams; 35 | 36 | #[rstest] 37 | #[case(Args { language: LanguageEnum::Node, wipe: false, ignores: Vec::new() })] 38 | #[case(Args { language: LanguageEnum::Node, wipe: true, ignores: Vec::new() })] 39 | #[case(Args { language: LanguageEnum::Node, wipe: true, ignores: vec![PathBuf::from("example/path")] })] 40 | #[case(Args { language: LanguageEnum::Rust, wipe: false, ignores: Vec::new() })] 41 | #[case(Args { language: LanguageEnum::Rust, wipe: true, ignores: Vec::new() })] 42 | #[case(Args { language: LanguageEnum::Rust, wipe: true, ignores: vec![PathBuf::from("example/path")] })] 43 | #[case(Args { language: LanguageEnum::Terraform, wipe: false, ignores: Vec::new() })] 44 | #[case(Args { language: LanguageEnum::Terraform, wipe: true, ignores: Vec::new() })] 45 | #[case(Args { language: LanguageEnum::Terraform, wipe: true, ignores: vec![PathBuf::from("example/path")] })] 46 | fn test_wipe_params(#[case] args: Args) { 47 | let params = WipeParams::new(&args).unwrap(); 48 | 49 | assert_eq!( 50 | params, 51 | WipeParams { 52 | wipe: args.wipe, 53 | path: std::env::current_dir().unwrap(), 54 | language: args.language, 55 | ignores: args.ignores, 56 | } 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/wipe.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::io; 3 | 4 | use crate::dir_helpers::DirInfo; 5 | use crate::wipe_params::WipeParams; 6 | use crate::writer::Writer; 7 | 8 | #[derive(Debug)] 9 | pub struct Wipe<'a, W> 10 | where 11 | W: io::Write, 12 | { 13 | writer: Writer<'a, W>, 14 | params: &'a WipeParams, 15 | } 16 | 17 | impl<'a, W> Wipe<'a, W> 18 | where 19 | W: io::Write, 20 | { 21 | pub fn new(stdout: &'a mut W, params: &'a WipeParams) -> Self { 22 | let writer = Writer::new(stdout); 23 | 24 | Self { writer, params } 25 | } 26 | 27 | pub fn run(&mut self) -> io::Result<()> { 28 | let writer = &mut self.writer; 29 | let params = self.params; 30 | 31 | writer.write_header(params)?; 32 | 33 | let paths_to_delete = DirInfo::get_paths_to_delete(¶ms.path, ¶ms.language)?; 34 | let paths_to_delete = paths_to_delete 35 | .iter() 36 | .filter_map(|p| p.as_ref().ok()) 37 | .collect::>(); 38 | 39 | let previous_info = if paths_to_delete.is_empty() { 40 | None 41 | } else { 42 | writer.write_content_header()?; 43 | Some(DirInfo::dir_size(¶ms.path)?) 44 | }; 45 | 46 | let mut wipe_info = DirInfo::new(paths_to_delete.len(), 0, 0); 47 | let mut ignore_info = DirInfo::new(0, 0, 0); 48 | 49 | let paths_ignored = params 50 | .ignores 51 | .iter() 52 | .map(|p| p.display().to_string().to_lowercase()) 53 | .collect::>(); 54 | 55 | for path in paths_to_delete { 56 | let dir_info = DirInfo::dir_size(path); 57 | 58 | let ignored = paths_ignored 59 | .iter() 60 | .any(|p| path.to_lowercase().starts_with(p)); 61 | 62 | let error = if !ignored && params.wipe { 63 | fs::remove_dir_all(path).err() 64 | } else { 65 | None 66 | }; 67 | 68 | if let Ok(dir_info) = dir_info { 69 | if ignored { 70 | ignore_info.dir_count += 1; 71 | ignore_info.file_count += dir_info.file_count; 72 | ignore_info.size += dir_info.size; 73 | } else if error.is_none() { 74 | wipe_info.file_count += dir_info.file_count; 75 | wipe_info.size += dir_info.size; 76 | } 77 | } 78 | 79 | writer.write_content_line(path, dir_info, ignored, error)?; 80 | } 81 | 82 | writer.write_summary(params, &wipe_info, &ignore_info, &previous_info)?; 83 | writer.write_footer(params, &wipe_info)?; 84 | 85 | Ok(()) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cargo Wipe 2 | 3 | [![Crates][crates_badge]][crates] 4 | [![CI][ci_badge]][ci] 5 | [![codecov][codecov_badge]][codecov] 6 | [![license][license_badge]][license] 7 | [![Crates.io][crates_installs_badge]][crates]\ 8 | Cargo subcommand that recursively finds and optionally wipes all "target" (Rust), 9 | "node_modules" (Node), or ".terraform" (Terraform) folders that are found in the current path. 10 | 11 | ## Usage 12 | 13 | ### Install 14 | 15 | The [Rust toolchain][toolchain] is a prerequisite. 16 | 17 | ```bash 18 | cargo install cargo-wipe 19 | ``` 20 | 21 | ### Read the docs 22 | 23 | ```bash 24 | cargo wipe --help 25 | ``` 26 | 27 | ### Use 28 | 29 | To find build folders for `` that can potentially be deleted run 30 | 31 | ```bash 32 | cargo wipe 33 | ``` 34 | 35 | where `` is `rust` or `node`. For example: 36 | 37 | ```bash 38 | cargo wipe rust 39 | ``` 40 | 41 | This will run in dry-run mode and just print the list of directories to delete. To actually delete them run it again with the `-w` flag. 42 | 43 | ```bash 44 | cargo wipe rust -w 45 | ``` 46 | 47 | Directories are found according to the following logic: 48 | 49 | - `rust`: all directories called `target` containing a file called `.rustc_info.json`. 50 | - `node`: all directories called `node_modules`. 51 | 52 | You can use the `-i ` argument to ignore certain paths. 53 | 54 | ### Usage Example 55 | 56 | ![Usage Example Screenshot][usage_example] 57 | 58 | ## Contributions 59 | 60 | Contributions are welcome and encouraged! See [/issues][issues] for ideas, or suggest your own! 61 | If you're thinking to create a PR with large feature/change, please first discuss it in an issue. 62 | 63 | ### PR Checks 64 | 65 | ```bash 66 | cargo make ci-flow 67 | ``` 68 | 69 | ### Releases 70 | 71 | - Update version in `Cargo.toml` 72 | - Update CHANGELOG.md 73 | - Commit 74 | - Add tag 75 | 76 | ```bash 77 | git tag -a vX.X.X 78 | ``` 79 | 80 | - Push 81 | 82 | ```bash 83 | git push --follow-tags 84 | ``` 85 | 86 | - Release\ 87 | Create a [new release](https://github.com/mihai-dinculescu/cargo-wipe/releases). \ 88 | `publish.yml` GitHub Action will pick it up and do the actual release to [crates.io][crates_io]. 89 | 90 | [crates_badge]: https://img.shields.io/crates/v/cargo-wipe.svg 91 | [crates]: https://crates.io/crates/cargo-wipe 92 | [ci_badge]: https://github.com/mihai-dinculescu/cargo-wipe/actions/workflows/ci.yml/badge.svg 93 | [ci]: https://github.com/mihai-dinculescu/cargo-wipe/actions/workflows/ci.yml 94 | [codecov_badge]: https://codecov.io/gh/mihai-dinculescu/cargo-wipe/branch/main/graph/badge.svg 95 | [codecov]: https://codecov.io/gh/mihai-dinculescu/cargo-wipe 96 | [license_badge]: https://img.shields.io/crates/l/cargo-wipe.svg 97 | [license]: https://github.com/mihai-dinculescu/cargo-wipe/blob/main/LICENSE 98 | [crates_installs_badge]: https://img.shields.io/crates/d/cargo-wipe?label=cargo%20installs 99 | [toolchain]: https://rustup.rs 100 | [usage_example]: https://github.com/mihai-dinculescu/cargo-wipe/blob/main/assets/screenshot.PNG 101 | [issues]: https://github.com/mihai-dinculescu/cargo-wipe/issues 102 | [crates_io]: https://crates.io 103 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this 4 | file. This change log follows the conventions of 5 | [keepachangelog.com](http://keepachangelog.com/). 6 | 7 | ## [Unreleased] 8 | 9 | ## [v0.4.0] - 2024-11-16 10 | 11 | ### Added 12 | 13 | - Added support for wiping `.terraform` folders in Terraform projects. Use the `terraform` language to target these folders. 14 | 15 | ### Fixed 16 | 17 | - Fixed an issue where folders that failed to delete were still counted as freed space. 18 | 19 | ### Removed 20 | 21 | - Removed the previously deprecated `target` and `node_modules` aliases. Use the `rust` and `node` aliases instead. 22 | 23 | ## [v0.3.3] - 2022-08-22 24 | 25 | ### Changed 26 | 27 | - All dependencies have been updated to their latest version. 28 | 29 | ## [v0.3.2] - 2021-10-31 30 | 31 | ### Changed 32 | 33 | - Upgraded to Rust 2021 34 | 35 | ### Fixed 36 | 37 | - The argument parsing has been improved so that it is less confusing 38 | 39 | ### Removed 40 | 41 | - The "target" and "node_modules" aliases have been deprecated and removed from the documentation. They will continue to work for a few more releases, but everyone is encouraged to switch to the preferred "rust" and "node" namings. 42 | 43 | ### Added 44 | 45 | - Specific paths can now be ignored by using the `i` argument 46 | 47 | ## [v0.3.1] - 2021-01-11 48 | 49 | ### Added 50 | 51 | - Specific paths can now be ignored by using the `i` argument 52 | 53 | ## [v0.3.0] - 2020-10-17 54 | 55 | ### Added 56 | 57 | - Before and after path disk size is now shown 58 | 59 | ### Changed 60 | 61 | - Total space (that can be) made available is now shown in an appropriate unit (bytes, KiB, MiB, GiB, etc.) 62 | - The `-w` flag can now be used both before and after the folder name argument 63 | - The folder name is now an argument instead of a subcommand 64 | 65 | ### Fixed 66 | 67 | - The `-w` flag now shows up in the root help section 68 | 69 | ## [v0.2.0] - 2020-05-24 70 | 71 | ### Added 72 | 73 | - Validation that checks if `target` folders are indeed Rust folders 74 | 75 | ### Removed 76 | 77 | - Extra warning for wiping `target` folders 78 | 79 | ### Fixed 80 | 81 | - Access denied errors are now handled gracefully 82 | - Fixed a crash caused by dirs without read permissions 83 | - Fixed the message displayed when empty directories are found 84 | 85 | ## [v0.1.3] - 2020-05-21 86 | 87 | ### Changed 88 | 89 | - Internal improvements 90 | 91 | ## [v0.1.2] - 2020-05-16 92 | 93 | ### Fixed 94 | 95 | - Fix wipe instructions 96 | 97 | ## [v0.1.1] - 2020-05-16 98 | 99 | ### Added 100 | 101 | - Extra warning for wiping `target` folders 102 | - `node_modules` and `target` subcommands 103 | 104 | ## [v0.1.0] - 2020-05-16 105 | 106 | ### Initial Release of cargo-wipe 107 | 108 | [unreleased]: https://github.com/mihai-dinculescu/cargo-wipe 109 | [v0.4.0]: https://github.com/mihai-dinculescu/cargo-wipe/tree/v0.4.0 110 | [v0.3.3]: https://github.com/mihai-dinculescu/cargo-wipe/tree/v0.3.3 111 | [v0.3.2]: https://github.com/mihai-dinculescu/cargo-wipe/tree/v0.3.2 112 | [v0.3.1]: https://github.com/mihai-dinculescu/cargo-wipe/tree/v0.3.1 113 | [v0.3.0]: https://github.com/mihai-dinculescu/cargo-wipe/tree/v0.3.0 114 | [v0.2.0]: https://github.com/mihai-dinculescu/cargo-wipe/tree/v0.2.0 115 | [v0.1.3]: https://github.com/mihai-dinculescu/cargo-wipe/tree/v0.1.3 116 | [v0.1.2]: https://github.com/mihai-dinculescu/cargo-wipe/tree/v0.1.2 117 | [v0.1.1]: https://github.com/mihai-dinculescu/cargo-wipe/tree/v0.1.1 118 | [v0.1.0]: https://github.com/mihai-dinculescu/cargo-wipe/tree/v0.1.0 119 | -------------------------------------------------------------------------------- /src/tests/helpers/test_run.rs: -------------------------------------------------------------------------------- 1 | use rand::{Rng, prelude::ThreadRng, rng}; 2 | use rand_distr::Alphanumeric; 3 | use std::path::{Path, PathBuf}; 4 | 5 | use crate::command::{DirectoryEnum, LanguageEnum}; 6 | 7 | #[derive(Debug)] 8 | pub struct TestRun { 9 | rng: ThreadRng, 10 | pub path: PathBuf, 11 | pub hits: Vec, 12 | pub ignores: Vec, 13 | pub misses: Vec, 14 | } 15 | 16 | impl Drop for TestRun { 17 | fn drop(&mut self) { 18 | std::fs::remove_dir_all(&self.path).unwrap(); 19 | } 20 | } 21 | 22 | impl From<&TestRun> for PathBuf { 23 | fn from(test_path: &TestRun) -> Self { 24 | test_path.path.clone() 25 | } 26 | } 27 | 28 | impl TestRun { 29 | pub fn new(language: &LanguageEnum, hits_count: u32, ignores_count: u32) -> Self { 30 | let mut rng = rng(); 31 | let name = TestRun::generate_folder_name(&mut rng); 32 | 33 | let path = std::env::temp_dir() 34 | .join(Path::new(".cargo-wipe-tests")) 35 | .join(Path::new(&name)); 36 | 37 | std::fs::create_dir_all(&path).unwrap(); 38 | 39 | let mut run = TestRun { 40 | rng, 41 | path, 42 | hits: Vec::new(), 43 | ignores: Vec::new(), 44 | misses: Vec::new(), 45 | }; 46 | 47 | run.generate_hits(language, hits_count); 48 | run.generate_ignores(language, ignores_count); 49 | run.generate_no_hits(); 50 | run.generate_different_language_hits(language); 51 | run.generate_invalid(language); 52 | run.generate_partial(language); 53 | 54 | run 55 | } 56 | 57 | fn generate_hits(&mut self, language: &LanguageEnum, hits_count: u32) { 58 | let directory: DirectoryEnum = language.into(); 59 | 60 | for _ in 0..hits_count { 61 | let name = TestRun::generate_folder_name(&mut self.rng); 62 | let path = self 63 | .path 64 | .join(Path::new(&name)) 65 | .join(Path::new(&directory.to_string())); 66 | 67 | std::fs::create_dir_all(&path).unwrap(); 68 | 69 | if language == &LanguageEnum::Rust { 70 | let file_path = path.join(".rustc_info.json"); 71 | std::fs::File::create(file_path).unwrap(); 72 | } 73 | 74 | self.hits.push(path); 75 | } 76 | } 77 | 78 | fn generate_ignores(&mut self, language: &LanguageEnum, ignores_count: u32) { 79 | let directory: DirectoryEnum = language.into(); 80 | 81 | for _ in 0..ignores_count { 82 | let name = TestRun::generate_folder_name(&mut self.rng); 83 | let path = self 84 | .path 85 | .join(Path::new(&name)) 86 | .join(Path::new(&directory.to_string())); 87 | 88 | std::fs::create_dir_all(&path).unwrap(); 89 | 90 | if language == &LanguageEnum::Rust { 91 | let file_path = path.join(".rustc_info.json"); 92 | std::fs::File::create(file_path).unwrap(); 93 | } 94 | 95 | self.ignores.push(path); 96 | } 97 | } 98 | 99 | fn generate_no_hits(&mut self) { 100 | for _ in 0..5 { 101 | let name = TestRun::generate_folder_name(&mut self.rng); 102 | let path = self.path.join(Path::new(&name)); 103 | 104 | std::fs::create_dir_all(&path).unwrap(); 105 | 106 | self.misses.push(path); 107 | } 108 | } 109 | 110 | fn generate_different_language_hits(&mut self, language: &LanguageEnum) { 111 | let different_language = if language == &LanguageEnum::Node { 112 | DirectoryEnum::Target.to_string() 113 | } else { 114 | DirectoryEnum::NodeModules.to_string() 115 | }; 116 | 117 | let name = TestRun::generate_folder_name(&mut self.rng); 118 | let path = self 119 | .path 120 | .join(Path::new(&name)) 121 | .join(Path::new(&different_language)); 122 | 123 | std::fs::create_dir_all(&path).unwrap(); 124 | 125 | self.misses.push(path); 126 | } 127 | 128 | fn generate_invalid(&mut self, language: &LanguageEnum) { 129 | let directory: DirectoryEnum = language.into(); 130 | 131 | if language == &LanguageEnum::Rust { 132 | let name = TestRun::generate_folder_name(&mut self.rng); 133 | let path = self 134 | .path 135 | .join(Path::new(&name)) 136 | .join(Path::new(&directory.to_string())); 137 | 138 | std::fs::create_dir_all(&path).unwrap(); 139 | 140 | self.misses.push(path); 141 | } 142 | } 143 | 144 | fn generate_partial(&mut self, language: &LanguageEnum) { 145 | let directory: DirectoryEnum = language.into(); 146 | 147 | let name = TestRun::generate_folder_name(&mut self.rng); 148 | let name_inner = TestRun::generate_folder_name(&mut self.rng); 149 | let name_inner = format!("{directory}_{name_inner}"); 150 | 151 | let path = self 152 | .path 153 | .join(Path::new(&name)) 154 | .join(Path::new(&name_inner)); 155 | 156 | std::fs::create_dir_all(&path).unwrap(); 157 | 158 | self.misses.push(path); 159 | } 160 | 161 | fn generate_folder_name(rng: &mut impl Rng) -> String { 162 | rng.sample_iter(&Alphanumeric) 163 | .take(16) 164 | .map(char::from) 165 | .collect() 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/dir_helpers.rs: -------------------------------------------------------------------------------- 1 | use num_format::{Locale, ToFormattedString}; 2 | use number_prefix::NumberPrefix; 3 | use std::path::PathBuf; 4 | use std::{fs, io}; 5 | 6 | use crate::command::{DirectoryEnum, LanguageEnum}; 7 | 8 | pub type PathsResult = io::Result>>; 9 | 10 | #[derive(Debug, Copy, Clone)] 11 | pub struct DirInfo { 12 | pub dir_count: usize, 13 | pub file_count: usize, 14 | pub size: usize, 15 | } 16 | 17 | impl DirInfo { 18 | pub fn new(dir_count: usize, file_count: usize, size: usize) -> Self { 19 | DirInfo { 20 | dir_count, 21 | file_count, 22 | size, 23 | } 24 | } 25 | 26 | pub fn file_count_formatted(&self) -> String { 27 | self.file_count.to_formatted_string(&Locale::en) 28 | } 29 | 30 | pub fn size_formatted_mb(&self) -> String { 31 | let num = self.size / 1024_usize.pow(2); 32 | num.to_formatted_string(&Locale::en) 33 | } 34 | 35 | pub fn size_formatted_flex(&self) -> String { 36 | let np = NumberPrefix::binary(self.size as f64); 37 | 38 | match np { 39 | NumberPrefix::Prefixed(prefix, n) => format!("{n:.2} {prefix}B"), 40 | NumberPrefix::Standalone(bytes) => format!("{bytes} bytes"), 41 | } 42 | } 43 | 44 | fn is_valid_target(path: PathBuf, directory: &DirectoryEnum) -> bool { 45 | if directory == &DirectoryEnum::Target { 46 | let file_path = path.join(".rustc_info.json"); 47 | return file_path.exists(); 48 | } 49 | 50 | true 51 | } 52 | 53 | pub fn get_paths_to_delete(path: impl Into, language: &LanguageEnum) -> PathsResult { 54 | let directory: DirectoryEnum = language.clone().into(); 55 | 56 | fn walk(dir: io::Result, directory: &DirectoryEnum) -> PathsResult { 57 | let mut dir = match dir { 58 | Ok(dir) => dir, 59 | Err(e) => { 60 | return Ok(vec![Err(e)]); 61 | } 62 | }; 63 | 64 | dir.try_fold( 65 | Vec::new(), 66 | |mut acc: Vec>, file| { 67 | let file = file?; 68 | 69 | let size = match file.metadata() { 70 | Ok(data) if data.is_dir() => { 71 | if file.file_name() == directory.to_string()[..] { 72 | if DirInfo::is_valid_target(file.path(), directory) { 73 | acc.push(Ok(file.path().display().to_string())); 74 | } 75 | } else { 76 | acc.append(&mut walk(fs::read_dir(file.path()), directory)?); 77 | } 78 | acc 79 | } 80 | _ => acc, 81 | }; 82 | 83 | Ok(size) 84 | }, 85 | ) 86 | } 87 | 88 | walk(fs::read_dir(path.into()), &directory) 89 | } 90 | 91 | pub fn dir_size(path: impl Into) -> io::Result { 92 | fn walk(dir: io::Result) -> io::Result { 93 | let mut dir = match dir { 94 | Ok(dir) => dir, 95 | Err(_) => { 96 | // Return empty stats for unreadable directories instead of failing 97 | return Ok(DirInfo::new(0, 0, 0)); 98 | } 99 | }; 100 | 101 | dir.try_fold(DirInfo::new(0, 0, 0), |acc, file| { 102 | let file = file?; 103 | 104 | let info = match file.metadata() { 105 | // For directories: count 1 directory + recursively count its contents 106 | Ok(data) if data.is_dir() => { 107 | let sub_info = walk(fs::read_dir(file.path()))?; 108 | DirInfo::new(1 + sub_info.dir_count, sub_info.file_count, sub_info.size) 109 | } 110 | // For files: count 1 file and its size in bytes 111 | Ok(data) => DirInfo::new(0, 1, data.len() as usize), 112 | // Skip entries we can't read metadata for 113 | _ => DirInfo::new(0, 0, 0), 114 | }; 115 | 116 | // Accumulate counts from this entry with running totals 117 | Ok(DirInfo::new( 118 | acc.dir_count + info.dir_count, 119 | acc.file_count + info.file_count, 120 | acc.size + info.size, 121 | )) 122 | }) 123 | } 124 | 125 | walk(fs::read_dir(path.into())) 126 | } 127 | } 128 | 129 | #[cfg(test)] 130 | mod tests { 131 | use super::*; 132 | use rstest::rstest; 133 | 134 | #[rstest] 135 | #[case(0, "0 bytes")] 136 | #[case(512, "512 bytes")] 137 | #[case(1024, "1.00 KiB")] 138 | #[case(1024_usize.pow(2), "1.00 MiB")] 139 | #[case(1024_usize.pow(3), "1.00 GiB")] 140 | #[case(1024_usize.pow(4), "1.00 TiB")] 141 | fn test_size_formatted_flex(#[case] size: usize, #[case] output: &str) { 142 | let di = DirInfo { 143 | dir_count: 0, 144 | file_count: 0, 145 | size, 146 | }; 147 | 148 | assert_eq!(di.size_formatted_flex(), output); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/command.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use std::{fmt, io, str}; 3 | 4 | use clap::{Parser, ValueEnum, arg, command}; 5 | 6 | #[derive(Debug, Parser)] 7 | #[command(name = "cargo", bin_name = "cargo")] 8 | pub enum Command { 9 | /// Recursively finds and optionally wipes all "target" (Rust), 10 | /// "node_modules" (Node), or ".terraform" (Terraform) folders that 11 | /// are found in the current path. 12 | /// Add the `-w` flag to wipe all folders found. USE WITH CAUTION! 13 | Wipe(Args), 14 | } 15 | 16 | #[derive(Debug, Parser)] 17 | #[command( 18 | version = env!("CARGO_PKG_VERSION"), 19 | bin_name = "cargo", 20 | help_template = "{before-help}{name} {version}\n{author-with-newline}{about-with-newline}\n{usage-heading} {usage}\n\n{all-args}{after-help}", 21 | )] 22 | pub struct Args { 23 | /// Language to target 24 | pub language: LanguageEnum, 25 | /// Caution! If set it will wipe all folders found! Unset by default 26 | #[arg(short, long)] 27 | pub wipe: bool, 28 | /// Absolute paths to ignore 29 | #[arg(short, long, value_parser)] 30 | pub ignores: Vec, 31 | } 32 | 33 | #[derive(Debug, PartialEq, Eq, Clone, ValueEnum)] 34 | pub enum LanguageEnum { 35 | Node, 36 | Rust, 37 | Terraform, 38 | } 39 | 40 | #[derive(Debug, PartialEq, Eq)] 41 | pub enum DirectoryEnum { 42 | NodeModules, 43 | Target, 44 | Terraform, 45 | } 46 | 47 | impl str::FromStr for LanguageEnum { 48 | type Err = io::Error; 49 | 50 | fn from_str(value: &str) -> Result { 51 | match value.to_lowercase().trim() { 52 | "node" => Ok(LanguageEnum::Node), 53 | "rust" => Ok(LanguageEnum::Rust), 54 | "terraform" => Ok(LanguageEnum::Terraform), 55 | _ => Err(io::Error::new( 56 | io::ErrorKind::InvalidInput, 57 | "Valid options are: node | rust | terraform", 58 | )), 59 | } 60 | } 61 | } 62 | 63 | impl fmt::Display for LanguageEnum { 64 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 65 | match self { 66 | LanguageEnum::Node => write!(f, "node"), 67 | LanguageEnum::Rust => write!(f, "rust"), 68 | LanguageEnum::Terraform => write!(f, "terraform"), 69 | } 70 | } 71 | } 72 | 73 | impl From for DirectoryEnum { 74 | fn from(language: LanguageEnum) -> Self { 75 | match language { 76 | LanguageEnum::Node => DirectoryEnum::NodeModules, 77 | LanguageEnum::Rust => DirectoryEnum::Target, 78 | LanguageEnum::Terraform => DirectoryEnum::Terraform, 79 | } 80 | } 81 | } 82 | 83 | impl From<&LanguageEnum> for DirectoryEnum { 84 | fn from(language: &LanguageEnum) -> Self { 85 | match language { 86 | LanguageEnum::Node => DirectoryEnum::NodeModules, 87 | LanguageEnum::Rust => DirectoryEnum::Target, 88 | LanguageEnum::Terraform => DirectoryEnum::Terraform, 89 | } 90 | } 91 | } 92 | 93 | impl fmt::Display for DirectoryEnum { 94 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 95 | match self { 96 | DirectoryEnum::NodeModules => write!(f, "node_modules"), 97 | DirectoryEnum::Target => write!(f, "target"), 98 | DirectoryEnum::Terraform => write!(f, ".terraform"), 99 | } 100 | } 101 | } 102 | 103 | #[cfg(test)] 104 | mod tests { 105 | use std::{io, str::FromStr}; 106 | 107 | use rstest::rstest; 108 | 109 | use crate::command::{DirectoryEnum, LanguageEnum}; 110 | 111 | #[rstest] 112 | #[case("node", LanguageEnum::Node)] 113 | #[case("rust", LanguageEnum::Rust)] 114 | #[case("terraform", LanguageEnum::Terraform)] 115 | #[case("RUST", LanguageEnum::Rust)] 116 | #[case("ruSt ", LanguageEnum::Rust)] 117 | fn language_string_to_enum(#[case] language_string: &str, #[case] language_enum: LanguageEnum) { 118 | assert_eq!( 119 | LanguageEnum::from_str(language_string).unwrap(), 120 | language_enum 121 | ); 122 | } 123 | 124 | #[rstest] 125 | #[case("node-modules")] 126 | #[case("rustt")] 127 | fn language_string_to_enum_error(#[case] language_string: &str) { 128 | let result = LanguageEnum::from_str(language_string); 129 | let err = result.err().unwrap(); 130 | 131 | assert_eq!(err.kind(), io::ErrorKind::InvalidInput); 132 | assert_eq!( 133 | err.to_string(), 134 | "Valid options are: node | rust | terraform" 135 | ); 136 | } 137 | 138 | #[rstest] 139 | #[case(LanguageEnum::Node, "node")] 140 | #[case(LanguageEnum::Rust, "rust")] 141 | #[case(LanguageEnum::Terraform, "terraform")] 142 | fn language_enum_to_string(#[case] language_enum: LanguageEnum, #[case] language_string: &str) { 143 | assert_eq!(language_enum.to_string(), language_string); 144 | } 145 | 146 | #[rstest] 147 | #[case(LanguageEnum::Node, DirectoryEnum::NodeModules)] 148 | #[case(LanguageEnum::Rust, DirectoryEnum::Target)] 149 | #[case(LanguageEnum::Terraform, DirectoryEnum::Terraform)] 150 | fn language_enum_to_directory_enum( 151 | #[case] language_enum: LanguageEnum, 152 | #[case] expected_directory_enum: DirectoryEnum, 153 | ) { 154 | let directory_enum: DirectoryEnum = language_enum.into(); 155 | assert_eq!(directory_enum, expected_directory_enum); 156 | } 157 | 158 | #[rstest] 159 | #[case(DirectoryEnum::NodeModules, "node_modules")] 160 | #[case(DirectoryEnum::Target, "target")] 161 | #[case(DirectoryEnum::Terraform, ".terraform")] 162 | fn directory_enum_to_string( 163 | #[case] directory_enum: DirectoryEnum, 164 | #[case] directory_string: &str, 165 | ) { 166 | assert_eq!(directory_enum.to_string(), directory_string); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/writer.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt::Display, io}; 2 | 3 | use yansi::Paint as _; 4 | 5 | use crate::command::DirectoryEnum; 6 | use crate::dir_helpers::DirInfo; 7 | use crate::wipe_params::WipeParams; 8 | 9 | pub const SPACING_FILES: usize = 12; 10 | pub const SPACING_SIZE: usize = 18; 11 | pub const SPACING_PATH: usize = 9; 12 | 13 | #[derive(Debug)] 14 | pub struct Writer<'a, W> 15 | where 16 | W: io::Write, 17 | { 18 | stdout: &'a mut W, 19 | } 20 | 21 | impl<'a, W> Writer<'a, W> 22 | where 23 | W: io::Write, 24 | { 25 | pub fn new(stdout: &'a mut W) -> Self { 26 | Self { stdout } 27 | } 28 | 29 | pub fn write_header(&mut self, params: &WipeParams) -> io::Result<()> { 30 | let directory: DirectoryEnum = params.language.clone().into(); 31 | 32 | let title = if params.wipe { 33 | "[WIPING]".red() 34 | } else { 35 | "[DRY RUN]".green() 36 | }; 37 | write!(self.stdout, "{}", title.bold())?; 38 | 39 | writeln!( 40 | self.stdout, 41 | r#" Recursively searching for all "{}" folders in {}..."#, 42 | &directory.cyan(), 43 | params.path.display().cyan(), 44 | )?; 45 | 46 | self.stdout.flush()?; 47 | Ok(()) 48 | } 49 | 50 | pub fn write_content_header(&mut self) -> io::Result<()> { 51 | writeln!(self.stdout)?; 52 | self.writeln_spaced_line("Files #".cyan(), "Size (MB)".cyan(), "", "Path".cyan())?; 53 | 54 | self.stdout.flush()?; 55 | Ok(()) 56 | } 57 | 58 | pub fn write_content_line( 59 | &mut self, 60 | path: &str, 61 | dir_info: Result, 62 | ignored: bool, 63 | result: Option, 64 | ) -> io::Result<()> { 65 | if let Ok(dir_info) = dir_info { 66 | self.write_spaced_line( 67 | dir_info.file_count_formatted(), 68 | dir_info.size_formatted_mb(), 69 | "", 70 | path, 71 | )?; 72 | } else { 73 | self.write_spaced_line("?", "?", "", path)?; 74 | } 75 | 76 | if ignored { 77 | write!(self.stdout, " {}", "[Ignored]".yellow())?; 78 | } 79 | 80 | if let Some(e) = result { 81 | write!(self.stdout, " {}", format!("[{e}]").red())?; 82 | } 83 | 84 | writeln!(self.stdout)?; 85 | self.stdout.flush()?; 86 | 87 | Ok(()) 88 | } 89 | 90 | pub fn write_summary( 91 | &mut self, 92 | params: &WipeParams, 93 | wipe_info: &DirInfo, 94 | ignore_info: &DirInfo, 95 | previous_info: &Option, 96 | ) -> io::Result<()> { 97 | writeln!(self.stdout)?; 98 | 99 | if wipe_info.dir_count > 0 { 100 | let previous_info = previous_info.expect("this should never be None"); 101 | 102 | let after = DirInfo { 103 | dir_count: previous_info.dir_count - wipe_info.dir_count, 104 | file_count: previous_info.file_count - wipe_info.file_count, 105 | size: previous_info.size - wipe_info.size, 106 | }; 107 | 108 | self.writeln_spaced_line( 109 | "Files #".cyan(), 110 | "Size".cyan(), 111 | "", 112 | params.path.display().cyan(), 113 | )?; 114 | 115 | let label = if params.wipe { 116 | "Previously" 117 | } else { 118 | "Currently" 119 | }; 120 | 121 | self.writeln_spaced_line( 122 | previous_info.file_count_formatted(), 123 | previous_info.size_formatted_flex(), 124 | "", 125 | label, 126 | )?; 127 | 128 | if ignore_info.dir_count > 0 { 129 | self.writeln_spaced_line( 130 | ignore_info.file_count_formatted().yellow(), 131 | ignore_info.size_formatted_flex().yellow(), 132 | "", 133 | "Ignored".yellow(), 134 | )?; 135 | } 136 | 137 | let label = if params.wipe { "Wiped" } else { "Can wipe" }; 138 | 139 | self.writeln_spaced_line( 140 | wipe_info.file_count_formatted().red(), 141 | wipe_info.size_formatted_flex().red(), 142 | "", 143 | label.red(), 144 | )?; 145 | 146 | let label = if params.wipe { "Now" } else { "After wipe" }; 147 | 148 | self.writeln_spaced_line( 149 | after.file_count_formatted().green(), 150 | after.size_formatted_flex().green(), 151 | "", 152 | label.green(), 153 | )?; 154 | 155 | writeln!(self.stdout)?; 156 | } 157 | 158 | self.stdout.flush()?; 159 | Ok(()) 160 | } 161 | 162 | pub fn write_footer(&mut self, params: &WipeParams, wipe_info: &DirInfo) -> io::Result<()> { 163 | if wipe_info.dir_count > 0 { 164 | if params.wipe { 165 | writeln!(self.stdout, "{}", "All clear!".green())? 166 | } else { 167 | writeln!( 168 | self.stdout, 169 | "Run {} to wipe all folders found. {}", 170 | format!("cargo wipe {} -w", params.language).red(), 171 | "USE WITH CAUTION!".red() 172 | )?; 173 | } 174 | } else { 175 | writeln!(self.stdout, "{}", "Nothing found!".green())? 176 | } 177 | 178 | self.stdout.flush()?; 179 | 180 | Ok(()) 181 | } 182 | 183 | fn write_spaced_line( 184 | &mut self, 185 | column_1: impl Display, 186 | column_2: impl Display, 187 | column_3: impl Display, 188 | column_4: impl Display, 189 | ) -> io::Result<()> { 190 | write!( 191 | self.stdout, 192 | r#"{column_1:>SPACING_FILES$}{column_2:>SPACING_SIZE$}{column_3:>SPACING_PATH$}{column_4}"#, 193 | )?; 194 | 195 | Ok(()) 196 | } 197 | 198 | fn writeln_spaced_line( 199 | &mut self, 200 | column_1: impl Display, 201 | column_2: impl Display, 202 | column_3: impl Display, 203 | column_4: impl Display, 204 | ) -> io::Result<()> { 205 | writeln!( 206 | self.stdout, 207 | r#"{column_1:>SPACING_FILES$}{column_2:>SPACING_SIZE$}{column_3:>SPACING_PATH$}{column_4}"#, 208 | )?; 209 | 210 | Ok(()) 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/tests/wipe.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use std::{io::Cursor, println}; 3 | 4 | use rstest::rstest; 5 | use yansi::Paint as _; 6 | 7 | use crate::command::{DirectoryEnum, LanguageEnum}; 8 | use crate::tests::helpers::test_run::TestRun; 9 | use crate::wipe::Wipe; 10 | use crate::wipe_params::WipeParams; 11 | use crate::writer::{SPACING_FILES, SPACING_SIZE}; 12 | 13 | #[rstest] 14 | #[case(LanguageEnum::Node, false)] 15 | #[case(LanguageEnum::Node, true)] 16 | #[case(LanguageEnum::Rust, false)] 17 | #[case(LanguageEnum::Rust, true)] 18 | #[case(LanguageEnum::Terraform, false)] 19 | #[case(LanguageEnum::Terraform, true)] 20 | fn run_with_hits(#[case] language: LanguageEnum, #[case] wipe: bool) { 21 | let test_run = TestRun::new(&language, 3, 0); 22 | let directory: DirectoryEnum = (&language).into(); 23 | 24 | let params = WipeParams { 25 | wipe, 26 | path: PathBuf::from(&test_run), 27 | language: language.clone(), 28 | ignores: Vec::new(), 29 | }; 30 | 31 | let mut buff = Cursor::new(Vec::new()); 32 | Wipe::new(&mut buff, ¶ms).run().unwrap(); 33 | 34 | let output = std::str::from_utf8(buff.get_ref()).unwrap(); 35 | println!("{output}"); 36 | 37 | // header 38 | let expected = format!("{}", "[DRY RUN]".green().bold()); 39 | assert_eq!(output.contains(&expected), !wipe); 40 | 41 | let expected = format!("{}", "[WIPING]".red().bold()); 42 | assert_eq!(output.contains(&expected), wipe); 43 | 44 | let expected = format!(r#""{}""#, directory.cyan()); 45 | assert!(output.contains(&expected)); 46 | 47 | // body 48 | // hits should be listed and wiped if wipe is true 49 | for path in &test_run.hits { 50 | let expected = String::from(path.to_str().unwrap()); 51 | assert!(output.contains(&expected)); 52 | assert_eq!(path.exists(), !wipe); 53 | } 54 | 55 | // misses should not be listed and not wiped 56 | for path in &test_run.misses { 57 | let expected = String::from(path.to_str().unwrap()); 58 | assert!(!output.contains(&expected)); 59 | assert!(path.exists()); 60 | } 61 | 62 | // summary should be displayed 63 | let expected = format!("{:>files$}", "Files #".cyan(), files = SPACING_FILES); 64 | let output = output.replacen(&expected, "", 1); 65 | assert!(output.contains(&expected)); 66 | 67 | let expected = format!("{:>size$}", "Size (MB)".cyan(), size = SPACING_SIZE); 68 | let output = output.replacen(&expected, "", 1); 69 | 70 | let expected = format!("{:>size$}", "Size".cyan(), size = SPACING_SIZE); 71 | assert!(output.contains(&expected)); 72 | 73 | let expected = format!("{}", test_run.path.display().cyan()); 74 | let output = &output.replacen(&expected, "", 1); 75 | assert!(output.contains(&expected)); 76 | 77 | let expected = format!("{}", "Ignored".yellow()); 78 | assert!(!output.contains(&expected)); 79 | 80 | // footer 81 | if wipe { 82 | let expected = format!("{}", "All clear!".green()); 83 | assert!(output.contains(&expected)); 84 | } else { 85 | let expected = format!( 86 | "Run {} to wipe all folders found. {}", 87 | format!("cargo wipe {} -w", params.language).red(), 88 | "USE WITH CAUTION!".red() 89 | ); 90 | assert!(output.contains(&expected)); 91 | } 92 | } 93 | 94 | #[rstest] 95 | #[case(LanguageEnum::Node, false)] 96 | #[case(LanguageEnum::Node, true)] 97 | #[case(LanguageEnum::Rust, false)] 98 | #[case(LanguageEnum::Rust, true)] 99 | #[case(LanguageEnum::Terraform, false)] 100 | #[case(LanguageEnum::Terraform, true)] 101 | fn run_no_hits(#[case] language: LanguageEnum, #[case] wipe: bool) { 102 | let test_run = TestRun::new(&language, 0, 0); 103 | 104 | let params = WipeParams { 105 | wipe, 106 | path: PathBuf::from(&test_run), 107 | language, 108 | ignores: Vec::new(), 109 | }; 110 | 111 | let mut buff = Cursor::new(Vec::new()); 112 | Wipe::new(&mut buff, ¶ms).run().unwrap(); 113 | 114 | let output = std::str::from_utf8(buff.get_ref()).unwrap(); 115 | println!("{output}"); 116 | 117 | // body 118 | let expected = format!("{}", "Files #".cyan()); 119 | assert!(!output.contains(&expected)); 120 | 121 | let expected = format!("{}", "Size".cyan()); 122 | assert!(!output.contains(&expected)); 123 | 124 | let expected = format!("{}", test_run.path.display().cyan()); 125 | let output = &output.replacen(&expected, "", 1); 126 | assert!(!output.contains(&expected)); 127 | 128 | // summary should not be displayed 129 | let expected = format!("{:>files$}", "Files #".cyan(), files = SPACING_FILES); 130 | let output = output.replacen(&expected, "", 1); 131 | assert!(!output.contains(&expected)); 132 | 133 | let expected = format!("{:>size$}", "Size (MB)".cyan(), size = SPACING_SIZE); 134 | let output = output.replacen(&expected, "", 1); 135 | 136 | let expected = format!("{:>size$}", "Size".cyan(), size = SPACING_SIZE); 137 | assert!(!output.contains(&expected)); 138 | 139 | let expected = format!("{}", test_run.path.display().cyan()); 140 | let output = &output.replacen(&expected, "", 1); 141 | assert!(!output.contains(&expected)); 142 | 143 | // footer 144 | let expected = format!("{}", "Nothing found!".green()); 145 | assert!(output.contains(&expected)); 146 | } 147 | 148 | #[rstest] 149 | #[case(LanguageEnum::Node, false)] 150 | #[case(LanguageEnum::Node, true)] 151 | #[case(LanguageEnum::Rust, false)] 152 | #[case(LanguageEnum::Rust, true)] 153 | #[case(LanguageEnum::Terraform, false)] 154 | #[case(LanguageEnum::Terraform, true)] 155 | fn run_with_ignores(#[case] language: LanguageEnum, #[case] wipe: bool) { 156 | let test_run = TestRun::new(&language, 3, 3); 157 | 158 | let params = WipeParams { 159 | wipe, 160 | path: PathBuf::from(&test_run), 161 | language, 162 | ignores: test_run.ignores.clone(), 163 | }; 164 | 165 | let mut buff = Cursor::new(Vec::new()); 166 | Wipe::new(&mut buff, ¶ms).run().unwrap(); 167 | 168 | let output = std::str::from_utf8(buff.get_ref()).unwrap(); 169 | let lines = output.lines(); 170 | println!("{output}"); 171 | 172 | // body 173 | // hits should be listed and wiped if wipe is true 174 | for path in &test_run.hits { 175 | let expected = String::from(path.to_str().unwrap()); 176 | let mut lines = lines.clone(); 177 | let line = lines.find(|l| l.contains(&expected)); 178 | 179 | assert!(line.is_some()); 180 | let line = line.unwrap(); 181 | 182 | assert!(line.contains(&expected)); 183 | assert!(!line.contains("[Ignored]")); 184 | assert_eq!(path.exists(), !wipe); 185 | } 186 | 187 | // ignores should be listed and not wiped if wipe is true 188 | for path in &test_run.ignores { 189 | let expected = String::from(path.to_str().unwrap()); 190 | let mut lines = lines.clone(); 191 | let line = lines.find(|l| l.contains(&expected)); 192 | 193 | assert!(line.is_some()); 194 | let line = line.unwrap(); 195 | 196 | assert!(line.contains(&expected)); 197 | assert!(line.contains("[Ignored]")); 198 | assert!(path.exists()); 199 | } 200 | 201 | // misses should not be listed and not wiped 202 | for path in &test_run.misses { 203 | let expected = String::from(path.to_str().unwrap()); 204 | let mut lines = lines.clone(); 205 | let line = lines.find(|l| l.contains(&expected)); 206 | 207 | assert!(line.is_none()); 208 | assert!(path.exists()); 209 | } 210 | 211 | // summary should be displayed 212 | let expected = format!("{}", "Ignored".yellow()); 213 | assert!(output.contains(&expected)); 214 | } 215 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.4" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anstream" 16 | version = "0.6.21" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" 19 | dependencies = [ 20 | "anstyle", 21 | "anstyle-parse", 22 | "anstyle-query", 23 | "anstyle-wincon", 24 | "colorchoice", 25 | "is_terminal_polyfill", 26 | "utf8parse", 27 | ] 28 | 29 | [[package]] 30 | name = "anstyle" 31 | version = "1.0.13" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" 34 | 35 | [[package]] 36 | name = "anstyle-parse" 37 | version = "0.2.7" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" 40 | dependencies = [ 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-query" 46 | version = "1.1.5" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" 49 | dependencies = [ 50 | "windows-sys", 51 | ] 52 | 53 | [[package]] 54 | name = "anstyle-wincon" 55 | version = "3.0.11" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" 58 | dependencies = [ 59 | "anstyle", 60 | "once_cell_polyfill", 61 | "windows-sys", 62 | ] 63 | 64 | [[package]] 65 | name = "anyhow" 66 | version = "1.0.100" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" 69 | 70 | [[package]] 71 | name = "arrayvec" 72 | version = "0.7.6" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" 75 | 76 | [[package]] 77 | name = "autocfg" 78 | version = "1.5.0" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 81 | 82 | [[package]] 83 | name = "cargo-wipe" 84 | version = "0.4.0" 85 | dependencies = [ 86 | "anyhow", 87 | "clap", 88 | "num-format", 89 | "number_prefix", 90 | "rand", 91 | "rand_distr", 92 | "rstest", 93 | "yansi", 94 | ] 95 | 96 | [[package]] 97 | name = "cfg-if" 98 | version = "1.0.4" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 101 | 102 | [[package]] 103 | name = "clap" 104 | version = "4.5.51" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" 107 | dependencies = [ 108 | "clap_builder", 109 | "clap_derive", 110 | ] 111 | 112 | [[package]] 113 | name = "clap_builder" 114 | version = "4.5.51" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" 117 | dependencies = [ 118 | "anstream", 119 | "anstyle", 120 | "clap_lex", 121 | "strsim", 122 | ] 123 | 124 | [[package]] 125 | name = "clap_derive" 126 | version = "4.5.49" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" 129 | dependencies = [ 130 | "heck", 131 | "proc-macro2", 132 | "quote", 133 | "syn", 134 | ] 135 | 136 | [[package]] 137 | name = "clap_lex" 138 | version = "0.7.6" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" 141 | 142 | [[package]] 143 | name = "colorchoice" 144 | version = "1.0.4" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" 147 | 148 | [[package]] 149 | name = "equivalent" 150 | version = "1.0.2" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 153 | 154 | [[package]] 155 | name = "futures-core" 156 | version = "0.3.31" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 159 | 160 | [[package]] 161 | name = "futures-macro" 162 | version = "0.3.31" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 165 | dependencies = [ 166 | "proc-macro2", 167 | "quote", 168 | "syn", 169 | ] 170 | 171 | [[package]] 172 | name = "futures-task" 173 | version = "0.3.31" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 176 | 177 | [[package]] 178 | name = "futures-timer" 179 | version = "3.0.3" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" 182 | 183 | [[package]] 184 | name = "futures-util" 185 | version = "0.3.31" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 188 | dependencies = [ 189 | "futures-core", 190 | "futures-macro", 191 | "futures-task", 192 | "pin-project-lite", 193 | "pin-utils", 194 | "slab", 195 | ] 196 | 197 | [[package]] 198 | name = "getrandom" 199 | version = "0.3.4" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" 202 | dependencies = [ 203 | "cfg-if", 204 | "libc", 205 | "r-efi", 206 | "wasip2", 207 | ] 208 | 209 | [[package]] 210 | name = "glob" 211 | version = "0.3.3" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" 214 | 215 | [[package]] 216 | name = "hashbrown" 217 | version = "0.16.0" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" 220 | 221 | [[package]] 222 | name = "heck" 223 | version = "0.5.0" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 226 | 227 | [[package]] 228 | name = "indexmap" 229 | version = "2.12.0" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" 232 | dependencies = [ 233 | "equivalent", 234 | "hashbrown", 235 | ] 236 | 237 | [[package]] 238 | name = "is_terminal_polyfill" 239 | version = "1.70.2" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" 242 | 243 | [[package]] 244 | name = "itoa" 245 | version = "1.0.15" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 248 | 249 | [[package]] 250 | name = "libc" 251 | version = "0.2.177" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" 254 | 255 | [[package]] 256 | name = "libm" 257 | version = "0.2.15" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" 260 | 261 | [[package]] 262 | name = "memchr" 263 | version = "2.7.6" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 266 | 267 | [[package]] 268 | name = "num-format" 269 | version = "0.4.4" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" 272 | dependencies = [ 273 | "arrayvec", 274 | "itoa", 275 | ] 276 | 277 | [[package]] 278 | name = "num-traits" 279 | version = "0.2.19" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 282 | dependencies = [ 283 | "autocfg", 284 | "libm", 285 | ] 286 | 287 | [[package]] 288 | name = "number_prefix" 289 | version = "0.4.0" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" 292 | 293 | [[package]] 294 | name = "once_cell_polyfill" 295 | version = "1.70.2" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" 298 | 299 | [[package]] 300 | name = "pin-project-lite" 301 | version = "0.2.16" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 304 | 305 | [[package]] 306 | name = "pin-utils" 307 | version = "0.1.0" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 310 | 311 | [[package]] 312 | name = "ppv-lite86" 313 | version = "0.2.21" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 316 | dependencies = [ 317 | "zerocopy", 318 | ] 319 | 320 | [[package]] 321 | name = "proc-macro-crate" 322 | version = "3.4.0" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" 325 | dependencies = [ 326 | "toml_edit", 327 | ] 328 | 329 | [[package]] 330 | name = "proc-macro2" 331 | version = "1.0.103" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" 334 | dependencies = [ 335 | "unicode-ident", 336 | ] 337 | 338 | [[package]] 339 | name = "quote" 340 | version = "1.0.42" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" 343 | dependencies = [ 344 | "proc-macro2", 345 | ] 346 | 347 | [[package]] 348 | name = "r-efi" 349 | version = "5.3.0" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" 352 | 353 | [[package]] 354 | name = "rand" 355 | version = "0.9.2" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" 358 | dependencies = [ 359 | "rand_chacha", 360 | "rand_core", 361 | ] 362 | 363 | [[package]] 364 | name = "rand_chacha" 365 | version = "0.9.0" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" 368 | dependencies = [ 369 | "ppv-lite86", 370 | "rand_core", 371 | ] 372 | 373 | [[package]] 374 | name = "rand_core" 375 | version = "0.9.3" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" 378 | dependencies = [ 379 | "getrandom", 380 | ] 381 | 382 | [[package]] 383 | name = "rand_distr" 384 | version = "0.5.1" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "6a8615d50dcf34fa31f7ab52692afec947c4dd0ab803cc87cb3b0b4570ff7463" 387 | dependencies = [ 388 | "num-traits", 389 | "rand", 390 | ] 391 | 392 | [[package]] 393 | name = "regex" 394 | version = "1.12.2" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" 397 | dependencies = [ 398 | "aho-corasick", 399 | "memchr", 400 | "regex-automata", 401 | "regex-syntax", 402 | ] 403 | 404 | [[package]] 405 | name = "regex-automata" 406 | version = "0.4.13" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" 409 | dependencies = [ 410 | "aho-corasick", 411 | "memchr", 412 | "regex-syntax", 413 | ] 414 | 415 | [[package]] 416 | name = "regex-syntax" 417 | version = "0.8.8" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" 420 | 421 | [[package]] 422 | name = "relative-path" 423 | version = "1.9.3" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" 426 | 427 | [[package]] 428 | name = "rstest" 429 | version = "0.26.1" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "f5a3193c063baaa2a95a33f03035c8a72b83d97a54916055ba22d35ed3839d49" 432 | dependencies = [ 433 | "futures-timer", 434 | "futures-util", 435 | "rstest_macros", 436 | ] 437 | 438 | [[package]] 439 | name = "rstest_macros" 440 | version = "0.26.1" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "9c845311f0ff7951c5506121a9ad75aec44d083c31583b2ea5a30bcb0b0abba0" 443 | dependencies = [ 444 | "cfg-if", 445 | "glob", 446 | "proc-macro-crate", 447 | "proc-macro2", 448 | "quote", 449 | "regex", 450 | "relative-path", 451 | "rustc_version", 452 | "syn", 453 | "unicode-ident", 454 | ] 455 | 456 | [[package]] 457 | name = "rustc_version" 458 | version = "0.4.1" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" 461 | dependencies = [ 462 | "semver", 463 | ] 464 | 465 | [[package]] 466 | name = "semver" 467 | version = "1.0.27" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" 470 | 471 | [[package]] 472 | name = "serde_core" 473 | version = "1.0.228" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 476 | dependencies = [ 477 | "serde_derive", 478 | ] 479 | 480 | [[package]] 481 | name = "serde_derive" 482 | version = "1.0.228" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 485 | dependencies = [ 486 | "proc-macro2", 487 | "quote", 488 | "syn", 489 | ] 490 | 491 | [[package]] 492 | name = "slab" 493 | version = "0.4.11" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" 496 | 497 | [[package]] 498 | name = "strsim" 499 | version = "0.11.1" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 502 | 503 | [[package]] 504 | name = "syn" 505 | version = "2.0.110" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" 508 | dependencies = [ 509 | "proc-macro2", 510 | "quote", 511 | "unicode-ident", 512 | ] 513 | 514 | [[package]] 515 | name = "toml_datetime" 516 | version = "0.7.3" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" 519 | dependencies = [ 520 | "serde_core", 521 | ] 522 | 523 | [[package]] 524 | name = "toml_edit" 525 | version = "0.23.7" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" 528 | dependencies = [ 529 | "indexmap", 530 | "toml_datetime", 531 | "toml_parser", 532 | "winnow", 533 | ] 534 | 535 | [[package]] 536 | name = "toml_parser" 537 | version = "1.0.4" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" 540 | dependencies = [ 541 | "winnow", 542 | ] 543 | 544 | [[package]] 545 | name = "unicode-ident" 546 | version = "1.0.22" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" 549 | 550 | [[package]] 551 | name = "utf8parse" 552 | version = "0.2.2" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 555 | 556 | [[package]] 557 | name = "wasip2" 558 | version = "1.0.1+wasi-0.2.4" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" 561 | dependencies = [ 562 | "wit-bindgen", 563 | ] 564 | 565 | [[package]] 566 | name = "windows-link" 567 | version = "0.2.1" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 570 | 571 | [[package]] 572 | name = "windows-sys" 573 | version = "0.61.2" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" 576 | dependencies = [ 577 | "windows-link", 578 | ] 579 | 580 | [[package]] 581 | name = "winnow" 582 | version = "0.7.13" 583 | source = "registry+https://github.com/rust-lang/crates.io-index" 584 | checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" 585 | dependencies = [ 586 | "memchr", 587 | ] 588 | 589 | [[package]] 590 | name = "wit-bindgen" 591 | version = "0.46.0" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" 594 | 595 | [[package]] 596 | name = "yansi" 597 | version = "1.0.1" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" 600 | 601 | [[package]] 602 | name = "zerocopy" 603 | version = "0.8.27" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" 606 | dependencies = [ 607 | "zerocopy-derive", 608 | ] 609 | 610 | [[package]] 611 | name = "zerocopy-derive" 612 | version = "0.8.27" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" 615 | dependencies = [ 616 | "proc-macro2", 617 | "quote", 618 | "syn", 619 | ] 620 | --------------------------------------------------------------------------------