diff options
author | Nick Gerace <nickagerace@gmail.com> | 2022-04-23 13:13:38 -0400 |
---|---|---|
committer | Nick Gerace <nickagerace@gmail.com> | 2022-04-23 13:47:02 -0400 |
commit | 6d4aa0bf924a030b10a6034658407b493d9cc762 (patch) | |
tree | de506df576ac225adfbc216cb1e0ce0491074b32 | |
parent | 4516907acec09ace341927c89d93d1f23c662e92 (diff) | |
download | gfold-6d4aa0bf924a030b10a6034658407b493d9cc762.zip |
Bolster loose bench to provide better results
Loose bench:
- Bolster loose bench to calculate more runs with averages
- Alternate order at the halfway mark of total runs for loose bench
- Display run averages and global duration for loose bench
- Add TODO for loose bench to track average deviation (i.e. how
consistently does gfold perform across runs?)
- Add module comment for loose bench explaining its purpose
Misc:
- Add TargetOption enum for processing target entries
- Name reports and target varaibles with more specificity
- Add fix commands to DEVELOPING
- Misc updates to Cargo.lock
- Update to 4.0.0-rc.3
Result:
- Only use "anyhow::Result" when there is more than one error type
Bug fix:
- Add current dir to scripts to ensure gfold is being built
Signed-off-by: Nick Gerace <nickagerace@gmail.com>
-rw-r--r-- | CHANGELOG.md | 5 | ||||
-rw-r--r-- | Cargo.lock | 23 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | docs/DEVELOPING.md | 8 | ||||
-rw-r--r-- | scripts/bench-loosely/src/main.rs | 148 | ||||
-rw-r--r-- | scripts/size/src/main.rs | 1 | ||||
-rw-r--r-- | src/cli.rs | 8 | ||||
-rw-r--r-- | src/config.rs | 11 | ||||
-rw-r--r-- | src/display.rs | 17 | ||||
-rw-r--r-- | src/main.rs | 3 | ||||
-rw-r--r-- | src/report.rs | 39 | ||||
-rw-r--r-- | src/report/target.rs | 44 | ||||
-rw-r--r-- | src/run.rs | 3 |
13 files changed, 208 insertions, 104 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b279a2..2aada48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Git path option for CLI and config file - `git` subcommand usage +### Notes + +- Substantial performance gains should be noticeable in certain scenarios +- Using `RUST_LOG` and `RUST_BACKTRACE` should be more helpful when debugging unexpected output, performance or suspected bugs + ### [3.0.0] - 2022-01-06 ### Added @@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "anyhow" -version = "1.0.56" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27" +checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" dependencies = [ "backtrace", ] @@ -74,9 +74,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.64" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e121dee8023ce33ab248d9ce1493df03c3b38a659b240096fcbd7048ff9c31f" +checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61" dependencies = [ "addr2line", "cc", @@ -213,7 +213,7 @@ dependencies = [ [[package]] name = "gfold" -version = "4.0.0-rc.2" +version = "4.0.0-rc.3" dependencies = [ "anyhow", "argh", @@ -306,9 +306,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.123" +version = "0.2.124" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb691a747a7ab48abc15c5b42066eaafde10dc427e3b6ee2a1cf43db04c763bd" +checksum = "21a41fed9d98f27ab1c6d161da622a4fa35e8a54a8adc24bbf3ddd0ef70b0e50" [[package]] name = "libgit2-sys" @@ -366,12 +366,11 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.4.4" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" dependencies = [ "adler", - "autocfg", ] [[package]] @@ -386,9 +385,9 @@ dependencies = [ [[package]] name = "object" -version = "0.27.1" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" +checksum = "40bec70ba014595f99f7aa110b84331ffe1ee9aece7fe6f387cc7e3ecda4d456" dependencies = [ "memchr", ] @@ -10,7 +10,7 @@ readme = "README.md" repository = "https://github.com/nickgerace/gfold/" edition = "2021" -version = "4.0.0-rc.2" +version = "4.0.0-rc.3" [dependencies] anyhow = { version = "1", features = ["backtrace"] } diff --git a/docs/DEVELOPING.md b/docs/DEVELOPING.md index 23b6607..51113ac 100644 --- a/docs/DEVELOPING.md +++ b/docs/DEVELOPING.md @@ -29,6 +29,13 @@ cargo build --all-targets > cargo nextest run > ``` +If you'd like to mass "fix" everything, you should commit/save existing work and execute the following: + +```shell +cargo fix --all-targets --all-features --allow-dirty --allow-staged +cargo clippy --fix --all-features --all-targets --allow-dirty --allow-staged +``` + ## Performance Checks Navigate to the [README in the `scripts` directory](../scripts/README.md) for more information on @@ -38,7 +45,6 @@ how to run performance checks. The following checks are optional and should be run occasionally. - ```shell # This command requires a nightly toolchain to be installed. cargo +nightly udeps diff --git a/scripts/bench-loosely/src/main.rs b/scripts/bench-loosely/src/main.rs index 37da22d..6613fb5 100644 --- a/scripts/bench-loosely/src/main.rs +++ b/scripts/bench-loosely/src/main.rs @@ -1,8 +1,19 @@ +//! This is the loose bench for gfold. Why use a loose bench over a precise one? Most of gfold +//! development happens with a mobile processor, which is prone to performance fluctuations over +//! background I/O operations, battery life and temperature. Moreover, the "real world" use case of +//! gfold is via CLI and not a library. Thus, this loose bench executes gfold as a CLI similarly +//! to real world use. This benchmark is not precise and is designed to give a high level overview. + +// TODO: add consistency deviation. We should see how far each result strays from the average. We +// want gfold to perform consistently as well as quickly. + use std::path::Path; use std::process::Command; use std::time::{Duration, Instant}; fn main() { + let global_instant = Instant::now(); + let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); let repo = manifest_dir .parent() @@ -14,6 +25,7 @@ fn main() { let output = Command::new("cargo") .arg("build") .arg("--release") + .current_dir(repo) .output() .expect("could not execute command"); if !output.status.success() { @@ -21,55 +33,127 @@ fn main() { } let binary = repo.join("target").join("release").join("gfold"); - let home = dirs::home_dir().expect("could not find home directory"); - let installed = home.join(".cargo").join("bin").join("gfold"); - let runs = 4; + let home_path = dirs::home_dir().expect("could not find home directory"); + let installed = home_path.join(".cargo").join("bin").join("gfold"); + let home = home_path.to_str().expect("could not convert to str"); + + // Add "1" to total runs to ensure caching does not skew results. We need one + // "warm up" run. + let runs = 41; // Loosely bench using the home directory as the target. + let mut home_new_durations = Vec::new(); + let mut home_old_durations = Vec::new(); + let mut first = true; for run in 0..runs { - println!( - "group {} of {} in {}", - run + 1, - runs, - home.to_str().expect("could not convert to str") - ); - loose_bench(&binary, &installed, &home); + println!("group {} of {} in {}", run + 1, runs, home); + + // Alternate at the halfway point. Though, it is doubtful that this would actually change anything. + // Only calculate the average after the first run to avoid caching skewing results. + if run > runs / 2 { + let (old_duration, new_duration) = loose_bench(&installed, &binary, &home_path); + if first { + first = false; + } else { + home_new_durations.push(new_duration); + home_old_durations.push(old_duration); + } + } else { + let (new_duration, old_duration) = loose_bench(&binary, &installed, &home_path); + if first { + first = false; + } else { + home_new_durations.push(new_duration); + home_old_durations.push(old_duration); + } + } } // Loosely bench with the parent directory of the repository as the target. let parent_of_repo = repo.parent().expect("could not get parent"); + let parent = parent_of_repo.to_str().expect("could not convert to str"); + let mut parent_new_durations = Vec::new(); + let mut parent_old_durations = Vec::new(); + let mut first = true; for run in 0..runs { - println!( - "group {} of {} in {}", - run + 1, - runs, - parent_of_repo.to_str().expect("could not convert to str") - ); - loose_bench(&binary, &installed, parent_of_repo); + println!("group {} of {} in {}", run + 1, runs, parent); + + // Alternate at the halfway point. Though, it is doubtful that this would actually change anything. + // Only calculate the average after the first run to avoid caching skewing results. + if run > runs / 2 { + let (old_duration, new_duration) = loose_bench(&installed, &binary, parent_of_repo); + if first { + first = false; + } else { + parent_new_durations.push(new_duration); + parent_old_durations.push(old_duration); + } + } else { + let (new_duration, old_duration) = loose_bench(&binary, &installed, parent_of_repo); + if first { + first = false; + } else { + parent_new_durations.push(new_duration); + parent_old_durations.push(old_duration); + } + } } + + // Print the averages. Start with a multi-platform empty newline print. + println!(); + println!( + "Average: {:?} - {} - {}", + average_duration(&home_new_durations), + home, + &binary.display(), + ); + println!( + "Average: {:?} - {} - {}", + average_duration(&home_old_durations), + home, + &installed.display(), + ); + println!( + "Average: {:?} - {} - {}", + average_duration(&parent_new_durations), + parent, + &binary.display(), + ); + println!( + "Average: {:?} - {} - {}", + average_duration(&parent_old_durations), + parent, + &installed.display(), + ); + + // Display the global duration for the entire script. Start with a multi-platform empty newline + // print. + println!(); + println!("Total duration: {:?}", global_instant.elapsed()); } -fn loose_bench(new: &Path, old: &Path, target: &Path) { - let new_duration = execute(new, target); - let old_duration = execute(old, target); - let (new_text, old_text) = match new_duration { - new_duration if new_duration > old_duration => ("LOST", "WON "), - new_duration if new_duration < old_duration => ("WON ", "LOST"), +fn loose_bench(first: &Path, second: &Path, target: &Path) -> (Duration, Duration) { + let first_duration = execute(first, target); + let second_duration = execute(second, target); + let (first_text, second_text) = match first_duration { + first_duration if first_duration > second_duration => ("LOST", "WON "), + first_duration if first_duration < second_duration => ("WON ", "LOST"), _ => ("TIE ", "TIE "), }; println!( " {} @ {:?} - {}", - new_text, - new_duration, - new.to_str().expect("could not convert to str"), + first_text, + first_duration, + first.to_str().expect("could not convert to str"), ); println!( " {} @ {:?} - {}", - old_text, - old_duration, - old.to_str().expect("could not convert to str"), + second_text, + second_duration, + second.to_str().expect("could not convert to str"), ); + (first_duration, second_duration) } fn execute(binary: &Path, target: &Path) -> Duration { @@ -88,3 +172,9 @@ fn execute(binary: &Path, target: &Path) -> Duration { duration } + +fn average_duration(durations: &[Duration]) -> Duration { + let sum = durations.iter().sum::<Duration>(); + let count = durations.len() as f32; + sum.div_f32(count) +} diff --git a/scripts/size/src/main.rs b/scripts/size/src/main.rs index 2d5f5e9..a92309a 100644 --- a/scripts/size/src/main.rs +++ b/scripts/size/src/main.rs @@ -16,6 +16,7 @@ fn main() { let output = Command::new("cargo") .arg("build") .arg("--release") + .current_dir(repo) .output() .expect("could not execute command"); if !output.status.success() { @@ -4,7 +4,6 @@ use crate::config::{ColorMode, Config, DisplayMode}; use crate::error::Error; use crate::run; -use anyhow::Result; use argh::FromArgs; use log::debug; use std::env; @@ -65,7 +64,7 @@ struct Args { /// Parse CLI arguments, initialize the logger, merge configurations as needed, and call /// [`run::run()`] with the resulting [`Config`]. -pub fn parse_and_run() -> Result<()> { +pub fn parse_and_run() -> anyhow::Result<()> { // First and foremost, get logging up and running. We want logs as quickly as possible for // debugging by setting "RUST_LOG". let args: Args = argh::from_env(); @@ -111,7 +110,8 @@ pub fn parse_and_run() -> Result<()> { debug!("finalized config options"); match &args.dry_run { - true => config.print(), - false => run::run(&config), + true => config.print()?, + false => run::run(&config)?, } + Ok(()) } diff --git a/src/config.rs b/src/config.rs index db3537a..bb4ac57 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,7 +1,6 @@ //! This module contains the config specification and functionality for creating a config. use crate::error::Error; -use anyhow::Result; use serde::{Deserialize, Serialize}; use std::path::PathBuf; use std::{env, fs, io}; @@ -67,7 +66,7 @@ impl Config { /// This method tries to deserialize the config file (empty, non-existent, partial or complete) /// and uses [`EntryConfig`] as an intermediary struct. This is the primary method used when /// creating a config. - pub fn try_config() -> Result<Self> { + pub fn try_config() -> anyhow::Result<Self> { // Within this method, we check if the config file is empty before deserializing it. Users // should be able to proceed with empty config files. If empty or not found, then we fall // back to the "EntryConfig" default before conversion. @@ -83,22 +82,22 @@ impl Config { _ => return Err(e.into()), }, }; - Self::from_entry_config(&entry_config) + Ok(Self::from_entry_config(&entry_config)?) } /// This method does not look for the config file and uses [`EntryConfig`]'s defaults instead. /// It is best for testing use and when the user wishes to skip config file lookup. - pub fn new() -> Result<Self> { + pub fn new() -> io::Result<Self> { Self::from_entry_config(&EntryConfig::default()) } /// This method prints the full config (merged with config file, as needed) as valid, pretty TOML. - pub fn print(self) -> Result<()> { + pub fn print(self) -> Result<(), toml::ser::Error> { print!("{}", toml::to_string_pretty(&self)?); Ok(()) } - fn from_entry_config(entry_config: &EntryConfig) -> Result<Self> { + fn from_entry_config(entry_config: &EntryConfig) -> io::Result<Self> { Ok(Config { path: match &entry_config.path { Some(s) => s.clone(), diff --git a/src/display.rs b/src/display.rs index 6440021..98f320b 100644 --- a/src/display.rs +++ b/src/display.rs @@ -4,9 +4,9 @@ use crate::config::{ColorMode, DisplayMode}; use crate::display::color::ColorHarness; use crate::error::Error; use crate::report::LabeledReports; -use anyhow::Result; use log::debug; use log::warn; +use std::io; use std::path::Path; mod color; @@ -19,16 +19,17 @@ pub fn display( display_mode: &DisplayMode, reports: &LabeledReports, color_mode: &ColorMode, -) -> Result<()> { +) -> anyhow::Result<()> { match display_mode { - DisplayMode::Standard => standard(reports, color_mode), - DisplayMode::Json => json(reports), - DisplayMode::Classic => classic(reports, color_mode), + DisplayMode::Standard => standard(reports, color_mode)?, + DisplayMode::Json => json(reports)?, + DisplayMode::Classic => classic(reports, color_mode)?, } + Ok(()) } /// Display [`LabeledReports`] to `stdout` in the standard (default) format. -fn standard(reports: &LabeledReports, color_mode: &ColorMode) -> Result<()> { +fn standard(reports: &LabeledReports, color_mode: &ColorMode) -> anyhow::Result<()> { debug!("detected standard display mode"); let mut all_reports = Vec::new(); for grouped_report in reports { @@ -72,7 +73,7 @@ fn standard(reports: &LabeledReports, color_mode: &ColorMode) -> Result<()> { } /// Display [`LabeledReports`] to `stdout` in JSON format. -fn json(reports: &LabeledReports) -> Result<()> { +fn json(reports: &LabeledReports) -> serde_json::error::Result<()> { debug!("detected json display mode"); let mut all_reports = Vec::new(); for grouped_report in reports { @@ -85,7 +86,7 @@ fn json(reports: &LabeledReports) -> Result<()> { } /// Display [`LabeledReports`] to `stdout` in the classic format. -fn classic(reports: &LabeledReports, color_mode: &ColorMode) -> Result<()> { +fn classic(reports: &LabeledReports, color_mode: &ColorMode) -> io::Result<()> { debug!("detected classic display mode"); let color_harness = ColorHarness::new(color_mode); diff --git a/src/main.rs b/src/main.rs index f1c7c72..dd48e98 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,6 @@ //! track of multiple Git repositories. The source code uses private modules rather than leveraging //! a library via `lib.rs`. -use anyhow::Result; use env_logger::Builder; use log::debug; use log::LevelFilter; @@ -18,7 +17,7 @@ mod status; /// Initializes the logger based on the debug flag and `RUST_LOG` environment variable and calls /// [`cli::parse_and_run()`] to generate a [`config::Config`] and eventually call [`run::run()`]. -fn main() -> Result<()> { +fn main() -> anyhow::Result<()> { match env::var("RUST_LOG").is_err() { true => Builder::new().filter_level(LevelFilter::Off).init(), false => env_logger::init(), diff --git a/src/report.rs b/src/report.rs index 70a2799..469ff62 100644 --- a/src/report.rs +++ b/src/report.rs @@ -3,7 +3,6 @@ use crate::config::DisplayMode; use crate::error::Error; use crate::status::Status; -use anyhow::Result; use git2::{ErrorCode, Reference, Remote, Repository, StatusOptions}; use log::{debug, trace}; use rayon::prelude::*; @@ -47,23 +46,21 @@ impl Report { status: &Status, url: Option<String>, email: Option<String>, - ) -> Result<Self> { + ) -> Result<Self, crate::error::Error> { Ok(Self { name: match path.file_name() { Some(s) => match s.to_str() { Some(s) => s.to_string(), - None => { - return Err(Error::FileNameToStrConversionFailure(path.to_path_buf()).into()) - } + None => return Err(Error::FileNameToStrConversionFailure(path.to_path_buf())), }, - None => return Err(Error::FileNameNotFound(path.to_path_buf()).into()), + None => return Err(Error::FileNameNotFound(path.to_path_buf())), }, branch: (*branch).into(), status: *status, parent: match path.parent() { Some(s) => match s.to_str() { Some(s) => Some(s.to_string()), - None => return Err(Error::PathToStrConversionFailure(s.to_path_buf()).into()), + None => return Err(Error::PathToStrConversionFailure(s.to_path_buf())), }, None => None, }, @@ -75,24 +72,26 @@ impl Report { /// Generate [`LabeledReports`] for a given path and its children. The [`DisplayMode`] is required /// because any two display modes can require differing amounts of data to be collected. -pub fn generate_reports(path: &Path, display_mode: &DisplayMode) -> Result<LabeledReports> { +pub fn generate_reports(path: &Path, display_mode: &DisplayMode) -> anyhow::Result<LabeledReports> { let include_email = match display_mode { DisplayMode::Standard | DisplayMode::Json => true, DisplayMode::Classic => false, }; - let unprocessed = target::recursive_target_gen(path)? + let unprocessed = target::generate_targets(path.to_path_buf())? .par_iter() .map(|path| generate_report(path, include_email)) - .collect::<Vec<Result<Report>>>(); + .collect::<Vec<anyhow::Result<Report>>>(); let mut processed = LabeledReports::new(); for wrapped_report in unprocessed { match wrapped_report { Ok(report) => { - if let Some(mut v) = processed.insert(report.parent.clone(), vec![report.clone()]) { - v.push(report.clone()); - processed.insert(report.parent, v); + if let Some(mut reports) = + processed.insert(report.parent.clone(), vec![report.clone()]) + { + reports.push(report.clone()); + processed.insert(report.parent, reports); } } Err(e) => return Err(e), @@ -102,7 +101,7 @@ pub fn generate_reports(path: &Path, display_mode: &DisplayMode) -> Result<Label } /// Generates a report with a given path. -fn generate_report(repo_path: &Path, include_email: bool) -> Result<Report> { +fn generate_report(repo_path: &Path, include_email: bool) -> anyhow::Result<Report> { debug!( "attemping to generate report for repository at path: {:?}", repo_path @@ -164,11 +163,15 @@ fn generate_report(repo_path: &Path, include_email: bool) -> Result<Report> { "finalized report collection for repository at path: {:?}", repo_path ); - Report::new(repo_path, branch, &status, url, email) + Ok(Report::new(repo_path, branch, &status, url, email)?) } /// Checks if local commit(s) on the current branch have not yet been pushed to the remote. -fn is_unpushed(repo: &Repository, head: &Reference, remote_name: &str) -> Result<bool> { +fn is_unpushed( + repo: &Repository, + head: &Reference, + remote_name: &str, +) -> Result<bool, git2::Error> { let local_head = head.peel_to_commit()?; let remote = format!( "{}/{}", @@ -227,7 +230,9 @@ fn get_email(repository: &Repository) -> Option<String> { None } -fn choose_remote_greedily(repository: &Repository) -> Result<(Option<Remote>, Option<String>)> { +fn choose_remote_greedily( + repository: &Repository, +) -> Result<(Option<Remote>, Option<String>), git2::Error> { let remotes = repository.remotes()?; Ok(match remotes.get(0) { Some(remote_name) => ( diff --git a/src/report/target.rs b/src/report/target.rs index 3401101..abc836a 100644 --- a/src/report/target.rs +++ b/src/report/target.rs @@ -3,16 +3,19 @@ use log::{debug, error, warn}; use rayon::prelude::*; use std::fs::DirEntry; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::{fs, io}; -/// This type represents bundled target Git directories that were generated from a given [`DirEntry`]. -type Targets = io::Result<Option<Vec<PathBuf>>>; +enum TargetOption { + Multiple(Vec<PathBuf>), + Single(PathBuf), + None, +} /// Ensure the entry is a directory and is not hidden. Then, check if a Git sub directory exists, /// which will indicate if the entry is a repository. Finally, generate targets based on that /// repository. -fn process_entry(entry: &DirEntry) -> Targets { +fn process_entry(entry: &DirEntry) -> io::Result<TargetOption> { match entry.file_type()?.is_dir() && !entry .file_name() @@ -26,17 +29,18 @@ fn process_entry(entry: &DirEntry) -> Targets { match git_sub_directory.exists() && git_sub_directory.is_dir() { true => { debug!("found target: {:?}", &path); - Ok(Some(vec![path])) + Ok(TargetOption::Single(path)) } - false => Ok(Some(recursive_target_gen(&path)?)), + false => Ok(TargetOption::Multiple(generate_targets(path)?)), } } - false => Ok(None), + false => Ok(TargetOption::None), } } -/// Recursive function for generating targets in a child directory. -pub fn recursive_target_gen(path: &Path) -> io::Result<Vec<PathBuf>> { +/// Generate targets from a given [`PathBuf`] based on its children (recursively). +/// We use recursion paired with [`rayon`] since we prioritize speed over memory use. +pub fn generate_targets(path: PathBuf) -> io::Result<Vec<PathBuf>> { let entries: Vec<DirEntry> = match fs::read_dir(&path) { Ok(o) => o.filter_map(|r| r.ok()).collect(), Err(e) if e.kind() == io::ErrorKind::PermissionDenied => { @@ -49,22 +53,18 @@ pub fn recursive_target_gen(path: &Path) -> io::Result<Vec<PathBuf>> { } }; - let targets = entries + let processed = entries .par_iter() .map(process_entry) - .collect::<Vec<Targets>>(); + .collect::<Vec<io::Result<TargetOption>>>(); - let mut results = vec![]; - for target in targets { - match target { - Ok(v) => { - if let Some(mut v) = v { - if !v.is_empty() { - results.append(&mut v); - } - } - } - Err(e) => return Err(e), + let mut results = Vec::new(); + for entry in processed { + let entry = entry?; + if let TargetOption::Multiple(targets) = entry { + results.extend(targets); + } else if let TargetOption::Single(target) = entry { + results.push(target); } } Ok(results) @@ -2,12 +2,11 @@ use crate::config::Config; use crate::{display, report}; -use anyhow::Result; /// This function is the primary entrypoint for the crate. It takes a given config and performs /// the end-to-end workflow using it. At this point, all CLI and config file options should be /// set, merged, ignored, etc. -pub fn run(config: &Config) -> Result<()> { +pub fn run(config: &Config) -> anyhow::Result<()> { let reports = report::generate_reports(&config.path, &config.display_mode)?; display::display(&config.display_mode, &reports, &config.color_mode) } |