diff options
author | Nick Gerace <nickagerace@gmail.com> | 2022-12-21 18:03:37 -0500 |
---|---|---|
committer | Nick Gerace <nickagerace@gmail.com> | 2022-12-21 18:22:35 -0500 |
commit | 5c297ada857562b30de3801b09fdbf697182eb76 (patch) | |
tree | 12f3fe6d641aea64cbe04fe88e8ef71f14f331aa | |
parent | 687b3f50d359c4225a5b4e4085c43d693e8a12d4 (diff) | |
download | gfold-5c297ada857562b30de3801b09fdbf697182eb76.zip |
Add submodule support in json mode
Primary:
- Add submodule collection and support in json display mode
Doc:
- Ensure build from source instructions work with cargo workspaces
- Add "no deps" flag to clippy usages
Misc:
- Add result type aliases
- Change NOTE, FIXME, and TODO comments to have the author's nickname
Signed-off-by: Nick Gerace <nickagerace@gmail.com>
-rw-r--r-- | CHANGELOG.md | 6 | ||||
-rw-r--r-- | crates/bench-loosely/src/main.rs | 4 | ||||
-rw-r--r-- | crates/gfold/src/cli.rs | 4 | ||||
-rw-r--r-- | crates/gfold/src/config.rs | 10 | ||||
-rw-r--r-- | crates/gfold/src/display.rs | 13 | ||||
-rw-r--r-- | crates/gfold/src/display/color.rs | 18 | ||||
-rw-r--r-- | crates/gfold/src/error.rs | 12 | ||||
-rw-r--r-- | crates/gfold/src/main.rs | 57 | ||||
-rw-r--r-- | crates/gfold/src/report.rs | 125 | ||||
-rw-r--r-- | crates/gfold/src/report/target.rs | 8 | ||||
-rw-r--r-- | crates/gfold/src/run.rs | 3 | ||||
-rw-r--r-- | docs/DEVELOPING.md | 4 | ||||
-rw-r--r-- | docs/MANUAL_INSTALL.md | 12 | ||||
-rw-r--r-- | docs/RELEASE.md | 4 |
14 files changed, 188 insertions, 92 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d686a3..792bbc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,11 @@ For new changes prior to version 4.0.0, please see [CHANGELOG_PRE_V4](./docs/CHA ## Unreleased -The latest version contains all changes. +<!-- The latest version contains all changes. --> + +### Added + +- Submodule information in the `json` display mode ## 4.2.0 - 2022-12-21 diff --git a/crates/bench-loosely/src/main.rs b/crates/bench-loosely/src/main.rs index 8ad46a3..52aa498 100644 --- a/crates/bench-loosely/src/main.rs +++ b/crates/bench-loosely/src/main.rs @@ -4,8 +4,8 @@ //! 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. +// TODO(nick): 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; diff --git a/crates/gfold/src/cli.rs b/crates/gfold/src/cli.rs index 09b58f1..d9bb593 100644 --- a/crates/gfold/src/cli.rs +++ b/crates/gfold/src/cli.rs @@ -6,7 +6,7 @@ use log::debug; use std::env; use crate::config::{ColorMode, Config, DisplayMode}; -use crate::error::Error; +use crate::error::{AnyhowResult, Error}; use crate::run; const HELP: &str = "\ @@ -59,7 +59,7 @@ struct Cli { /// Parse CLI arguments, initialize the logger, merge configurations as needed, and call /// [`run::run()`] with the resulting [`Config`]. -pub fn parse_and_run() -> anyhow::Result<()> { +pub fn parse_and_run() -> AnyhowResult<()> { // First and foremost, get logging up and running. We want logs as quickly as possible for // debugging by setting "RUST_LOG". let cli = Cli::parse(); diff --git a/crates/gfold/src/config.rs b/crates/gfold/src/config.rs index 7b8d472..4eba4e4 100644 --- a/crates/gfold/src/config.rs +++ b/crates/gfold/src/config.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use std::path::PathBuf; use std::{env, fs, io}; -use crate::error::Error; +use crate::error::{AnyhowResult, Error, IoResult, TomlResult}; /// This struct is the actual config type consumed through the codebase. It is boostrapped via its /// public methods and uses [`EntryConfig`], a private struct, under the hood in order to @@ -67,7 +67,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() -> anyhow::Result<Self> { + pub fn try_config() -> AnyhowResult<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. @@ -88,17 +88,17 @@ impl Config { /// This method does not look for the config file and uses [`EntryConfig`]'s defaults instead. /// Use this method when the user wishes to skip config file lookup. - pub fn try_config_default() -> io::Result<Self> { + pub fn try_config_default() -> IoResult<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<(), toml::ser::Error> { + pub fn print(self) -> TomlResult<()> { print!("{}", toml::to_string_pretty(&self)?); Ok(()) } - fn from_entry_config(entry_config: &EntryConfig) -> io::Result<Self> { + fn from_entry_config(entry_config: &EntryConfig) -> IoResult<Self> { Ok(Config { path: match &entry_config.path { Some(s) => s.clone(), diff --git a/crates/gfold/src/display.rs b/crates/gfold/src/display.rs index 40f088e..a613c3a 100644 --- a/crates/gfold/src/display.rs +++ b/crates/gfold/src/display.rs @@ -2,12 +2,11 @@ use log::debug; use log::warn; -use std::io; use std::path::Path; use crate::config::{ColorMode, DisplayMode}; use crate::display::color::ColorHarness; -use crate::error::Error; +use crate::error::{AnyhowResult, Error, IoResult, SerdeJsonResult}; use crate::report::LabeledReports; mod color; @@ -20,7 +19,7 @@ pub fn display( display_mode: &DisplayMode, reports: &LabeledReports, color_mode: &ColorMode, -) -> anyhow::Result<()> { +) -> AnyhowResult<()> { match display_mode { DisplayMode::Standard => standard(reports, color_mode)?, DisplayMode::Json => json(reports)?, @@ -30,7 +29,7 @@ pub fn display( } /// Display [`LabeledReports`] to `stdout` in the standard (default) format. -fn standard(reports: &LabeledReports, color_mode: &ColorMode) -> anyhow::Result<()> { +fn standard(reports: &LabeledReports, color_mode: &ColorMode) -> AnyhowResult<()> { debug!("detected standard display mode"); let mut all_reports = Vec::new(); for grouped_report in reports { @@ -74,7 +73,7 @@ fn standard(reports: &LabeledReports, color_mode: &ColorMode) -> anyhow::Result< } /// Display [`LabeledReports`] to `stdout` in JSON format. -fn json(reports: &LabeledReports) -> serde_json::error::Result<()> { +fn json(reports: &LabeledReports) -> SerdeJsonResult<()> { debug!("detected json display mode"); let mut all_reports = Vec::new(); for grouped_report in reports { @@ -87,14 +86,14 @@ fn json(reports: &LabeledReports) -> serde_json::error::Result<()> { } /// Display [`LabeledReports`] to `stdout` in the classic format. -fn classic(reports: &LabeledReports, color_mode: &ColorMode) -> io::Result<()> { +fn classic(reports: &LabeledReports, color_mode: &ColorMode) -> IoResult<()> { debug!("detected classic display mode"); let color_harness = ColorHarness::new(color_mode); let length = reports.keys().len(); let mut first = true; for (title, group) in reports { - // FIXME: make group title matching less cumbersome. + // FIXME(nick): make group title matching less cumbersome. if length > 1 { match first { true => { diff --git a/crates/gfold/src/display/color.rs b/crates/gfold/src/display/color.rs index 6072963..153b8ab 100644 --- a/crates/gfold/src/display/color.rs +++ b/crates/gfold/src/display/color.rs @@ -1,9 +1,10 @@ //! This module provides a harness for non-trivial displays of information to `stdout`. -use std::io::{self, Write}; +use std::io::Write; use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; use crate::config::ColorMode; +use crate::error::IoResult; use crate::status::Status; /// This harness provides methods to write to `stdout`. It maps the internal [`ColorMode`] type to @@ -24,7 +25,7 @@ impl ColorHarness { } /// Writes the [`Status`] of the Git repository to `stdout`. - pub fn write_status(&self, status: &Status, status_width: usize) -> io::Result<()> { + pub fn write_status(&self, status: &Status, status_width: usize) -> IoResult<()> { let mut stdout = StandardStream::stdout(self.color_choice); stdout.set_color(ColorSpec::new().set_fg(Some(match status { Status::Bare | Status::Unknown => Color::Red, @@ -42,13 +43,13 @@ impl ColorHarness { } /// Writes the input [`&str`] to `stdout` in bold. - pub fn write_bold(&self, input: &str, newline: bool) -> io::Result<()> { + pub fn write_bold(&self, input: &str, newline: bool) -> IoResult<()> { self.write_color(input, newline, ColorSpec::new().set_bold(true)) } /// Writes the input [`&str`] to `stdout` in gray (or cyan if in compatibility mode). - pub fn write_gray(&self, input: &str, newline: bool) -> io::Result<()> { - // FIXME: check why Color::Rg(128, 128, 128) breaks in tmux on macOS Terminal.app. + pub fn write_gray(&self, input: &str, newline: bool) -> IoResult<()> { + // FIXME(nick): check why Color::Rg(128, 128, 128) breaks in tmux on macOS Terminal.app. self.write_color( input, newline, @@ -59,12 +60,7 @@ impl ColorHarness { ) } - fn write_color( - &self, - input: &str, - newline: bool, - color_spec: &mut ColorSpec, - ) -> io::Result<()> { + fn write_color(&self, input: &str, newline: bool, color_spec: &mut ColorSpec) -> IoResult<()> { let mut stdout = StandardStream::stdout(self.color_choice); stdout.set_color(color_spec)?; match newline { diff --git a/crates/gfold/src/error.rs b/crates/gfold/src/error.rs index 6eeda29..dd2ec3a 100644 --- a/crates/gfold/src/error.rs +++ b/crates/gfold/src/error.rs @@ -3,6 +3,16 @@ use std::path::PathBuf; use thiserror::Error; +// Type aliases for external results. +pub type AnyhowResult<T> = anyhow::Result<T>; +pub type IoResult<T> = std::io::Result<T>; +pub type LibGitResult<T> = std::result::Result<T, git2::Error>; +pub type SerdeJsonResult<T> = serde_json::error::Result<T>; +pub type TomlResult<T> = std::result::Result<T, toml::ser::Error>; + +// Type alias for internal errors. +pub type Result<T> = std::result::Result<T, Error>; + #[derive(Error, Debug)] pub enum Error { #[error("received None (Option<&OsStr>) for file name: {0}")] @@ -19,6 +29,8 @@ pub enum Error { #[error("full shorthand for Git reference is invalid UTF-8")] GitReferenceShorthandInvalid, + #[error("submodule name is invalid UTF-8")] + SubmoduleNameInvalid, #[error("could not find home directory")] HomeDirNotFound, } diff --git a/crates/gfold/src/main.rs b/crates/gfold/src/main.rs index 6b7bdb6..074d357 100644 --- a/crates/gfold/src/main.rs +++ b/crates/gfold/src/main.rs @@ -7,6 +7,8 @@ use log::debug; use log::LevelFilter; use std::env; +use crate::error::AnyhowResult; + mod cli; mod config; mod display; @@ -17,7 +19,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() -> anyhow::Result<()> { +fn main() -> AnyhowResult<()> { match env::var("RUST_LOG").is_err() { true => Builder::new().filter_level(LevelFilter::Off).init(), false => env_logger::init(), @@ -36,13 +38,11 @@ mod tests { use crate::report::{LabeledReports, Report}; use crate::status::Status; - use anyhow::{anyhow, Result}; - + use anyhow::anyhow; use git2::ErrorCode; use git2::Oid; use git2::Repository; use git2::Signature; - use pretty_assertions::assert_eq; use std::collections::BTreeMap; use std::fs::File; @@ -54,7 +54,7 @@ mod tests { /// [`tempfile`](tempfile) crate to create some repositories with varying states and levels /// of nesting. #[test] - fn integration() -> Result<()> { + fn integration() -> AnyhowResult<()> { env_logger::builder() .is_test(true) .filter_level(LevelFilter::Info) @@ -138,9 +138,30 @@ mod tests { .ok_or_else(|| anyhow!("could not convert PathBuf to &str"))? .to_string(); let mut expected_reports_raw = vec![ - Report::new(&repo_one, "HEAD", &Status::Unclean, None, None)?, - Report::new(&repo_two, "HEAD", &Status::Clean, None, None)?, - Report::new(&repo_three, "HEAD", &Status::Clean, None, None)?, + Report::new( + &repo_one, + "HEAD", + &Status::Unclean, + None, + None, + Vec::with_capacity(0), + )?, + Report::new( + &repo_two, + "HEAD", + &Status::Clean, + None, + None, + Vec::with_capacity(0), + )?, + Report::new( + &repo_three, + "HEAD", + &Status::Clean, + None, + None, + Vec::with_capacity(0), + )?, ]; expected_reports_raw.sort_by(|a, b| a.name.cmp(&b.name)); expected_reports.insert(Some(expected_reports_key), expected_reports_raw); @@ -157,14 +178,23 @@ mod tests { &Status::Clean, Some("https://github.com/nickgerace/gfold".to_string()), None, + Vec::with_capacity(0), + )?, + Report::new( + &repo_five, + "HEAD", + &Status::Unclean, + None, + None, + Vec::with_capacity(0), )?, - Report::new(&repo_five, "HEAD", &Status::Unclean, None, None)?, Report::new( &repo_six, "master", &Status::Unpushed, Some("https://github.com/nickgerace/gfold".to_string()), None, + Vec::with_capacity(0), )?, Report::new( &repo_seven, @@ -172,6 +202,7 @@ mod tests { &Status::Unpushed, Some("https://github.com/nickgerace/gfold".to_string()), None, + Vec::with_capacity(0), )?, ]; nested_expected_reports_raw.sort_by(|a, b| a.name.cmp(&b.name)); @@ -197,7 +228,7 @@ mod tests { Ok(()) } - fn create_directory<P: AsRef<Path>>(parent: P, name: &str) -> Result<PathBuf> { + fn create_directory<P: AsRef<Path>>(parent: P, name: &str) -> AnyhowResult<PathBuf> { let parent = parent.as_ref(); let new_directory = parent.join(name); @@ -213,13 +244,13 @@ mod tests { Ok(new_directory) } - fn create_file<P: AsRef<Path>>(parent: P) -> Result<()> { + fn create_file<P: AsRef<Path>>(parent: P) -> AnyhowResult<()> { let parent = parent.as_ref(); File::create(parent.join("file"))?; Ok(()) } - fn commit_head_and_create_branch(repository: &Repository, name: &str) -> Result<()> { + fn commit_head_and_create_branch(repository: &Repository, name: &str) -> AnyhowResult<()> { // We need to commit at least once before branching. let commit_oid = commit(repository, "HEAD")?; let commit = repository.find_commit(commit_oid)?; @@ -228,7 +259,7 @@ mod tests { } // Source: https://github.com/rust-lang/git2-rs/pull/885 - fn commit(repository: &Repository, update_ref: &str) -> Result<Oid> { + fn commit(repository: &Repository, update_ref: &str) -> AnyhowResult<Oid> { // We will commit the contents of the index. let mut index = repository.index()?; let tree_oid = index.write_tree()?; diff --git a/crates/gfold/src/report.rs b/crates/gfold/src/report.rs index df8f523..e72a995 100644 --- a/crates/gfold/src/report.rs +++ b/crates/gfold/src/report.rs @@ -8,7 +8,7 @@ use std::collections::BTreeMap; use std::path::Path; use crate::config::DisplayMode; -use crate::error::Error; +use crate::error::{AnyhowResult, Error, LibGitResult, Result}; use crate::status::Status; mod target; @@ -18,7 +18,7 @@ const HEAD: &str = "HEAD"; /// This type represents a [`BTreeMap`] using an optional [`String`] for keys, which represents the /// parent directory for a group of reports ([`Vec<Report>`]). The values corresponding to those keys /// are the actual groups of reports. -// NOTE: We use a BTreeMap over a HashMap for sorted keys. +// NOTE(nick): We use a BTreeMap over a HashMap for sorted keys. pub type LabeledReports = BTreeMap<Option<String>, Vec<Report>>; /// A collection of results for a Git repository at a given path. @@ -36,8 +36,17 @@ pub struct Report { /// The remote origin URL. The value will be `None` if the URL cannot be found. pub url: Option<String>, - /// The "user.email" of a Git config that's only collected when using [`DisplayMode::Standard`]. + /// The "user.email" of a Git config that's only collected when using [`DisplayMode::Standard`] + /// and [`DisplayMode::Json`]. pub email: Option<String>, + /// The submodules of a repository that are only collected when using [`DisplayMode::Json`]. + pub submodules: Vec<SubmoduleReport>, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct SubmoduleReport { + pub name: String, + pub status: Status, } impl Report { @@ -47,7 +56,8 @@ impl Report { status: &Status, url: Option<String>, email: Option<String>, - ) -> Result<Self, crate::error::Error> { + submodules: Vec<SubmoduleReport>, + ) -> Result<Self> { Ok(Self { name: match path.file_name() { Some(s) => match s.to_str() { @@ -67,22 +77,24 @@ impl Report { }, url, email, + submodules, }) } } /// 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) -> anyhow::Result<LabeledReports> { - let include_email = match display_mode { - DisplayMode::Standard | DisplayMode::Json => true, - DisplayMode::Classic => false, +pub fn generate_reports(path: &Path, display_mode: &DisplayMode) -> AnyhowResult<LabeledReports> { + let (include_email, include_submodules) = match display_mode { + DisplayMode::Classic => (false, false), + DisplayMode::Json => (true, true), + DisplayMode::Standard => (true, false), }; let unprocessed = target::generate_targets(path.to_path_buf())? .par_iter() - .map(|path| generate_report(path, include_email)) - .collect::<Vec<anyhow::Result<Report>>>(); + .map(|path| generate_report(path, include_email, include_submodules)) + .collect::<Vec<AnyhowResult<Report>>>(); let mut processed = LabeledReports::new(); for wrapped_report in unprocessed { @@ -102,7 +114,11 @@ pub fn generate_reports(path: &Path, display_mode: &DisplayMode) -> anyhow::Resu } /// Generates a report with a given path. -fn generate_report(repo_path: &Path, include_email: bool) -> anyhow::Result<Report> { +fn generate_report( + repo_path: &Path, + include_email: bool, + include_submodules: bool, +) -> AnyhowResult<Report> { debug!( "attempting to generate report for repository at path: {:?}", repo_path @@ -112,18 +128,36 @@ fn generate_report(repo_path: &Path, include_email: bool) -> anyhow::Result<Repo Ok(repo) => repo, Err(e) if e.message() == "unsupported extension name extensions.worktreeconfig" => { error!("skipping error ({e}) until upstream libgit2 issue is resolved: https://github.com/libgit2/libgit2/issues/6044"); - let unknown_report = Report::new(repo_path, "unknown", &Status::Unknown, None, None)?; + let unknown_report = Report::new( + repo_path, + "unknown", + &Status::Unknown, + None, + None, + Vec::with_capacity(0), + )?; return Ok(unknown_report); } Err(e) => return Err(e.into()), }; + let (status, head, remote) = find_status(&repo)?; - let head = match repo.head() { - Ok(head) => Some(head), - Err(ref e) if e.code() == ErrorCode::UnbornBranch || e.code() == ErrorCode::NotFound => { - None + let submodules = if include_submodules { + let mut submodules = Vec::new(); + for submodule in repo.submodules()? { + if let Ok(subrepo) = submodule.open() { + let (status, _, _) = find_status(&subrepo)?; + let name = submodule.name().ok_or(Error::SubmoduleNameInvalid)?; + + submodules.push(SubmoduleReport { + name: name.to_string(), + status, + }); + } } - Err(e) => return Err(e.into()), + submodules + } else { + Vec::with_capacity(0) }; let branch = match &head { @@ -133,16 +167,42 @@ fn generate_report(repo_path: &Path, include_email: bool) -> anyhow::Result<Repo None => HEAD, }; + let url = match remote { + Some(remote) => remote.url().map(|s| s.to_string()), + None => None, + }; + + let email = match include_email { + true => get_email(&repo), + false => None, + }; + + debug!( + "finalized report collection for repository at path: {:?}", + repo_path + ); + Ok(Report::new( + repo_path, branch, &status, url, email, submodules, + )?) +} + +/// Find the [`Status`] for a given [`Repository`](git2::Repository). The +/// [`head`](Option<git2::Reference>) and [`remote`](Option<git2::Remote>) are also returned. +fn find_status(repo: &Repository) -> AnyhowResult<(Status, Option<Reference>, Option<Remote>)> { + let head = match repo.head() { + Ok(head) => Some(head), + Err(ref e) if e.code() == ErrorCode::UnbornBranch || e.code() == ErrorCode::NotFound => { + None + } + Err(e) => return Err(e.into()), + }; + // Greedily chooses a remote if "origin" is not found. let (remote, remote_name) = match repo.find_remote("origin") { Ok(origin) => (Some(origin), Some("origin".to_string())), - Err(e) if e.code() == ErrorCode::NotFound => choose_remote_greedily(&repo)?, + Err(e) if e.code() == ErrorCode::NotFound => choose_remote_greedily(repo)?, Err(e) => return Err(e.into()), }; - let url = match remote { - Some(remote) => remote.url().map(|s| s.to_string()), - None => None, - }; // We'll include all untracked files and directories in the status options. let mut opts = StatusOptions::new(); @@ -153,7 +213,7 @@ fn generate_report(repo_path: &Path, include_email: bool) -> anyhow::Result<Repo let status = match repo.statuses(Some(&mut opts)) { Ok(v) if v.is_empty() => match &head { Some(head) => match remote_name { - Some(remote_name) => match is_unpushed(&repo, head, &remote_name)? { + Some(remote_name) => match is_unpushed(repo, head, &remote_name)? { true => Status::Unpushed, false => Status::Clean, }, @@ -166,24 +226,11 @@ fn generate_report(repo_path: &Path, include_email: bool) -> anyhow::Result<Repo Err(e) => return Err(e.into()), }; - let email = match include_email { - true => get_email(&repo), - false => None, - }; - - debug!( - "finalized report collection for repository at path: {:?}", - repo_path - ); - Ok(Report::new(repo_path, branch, &status, url, email)?) + Ok((status, head, remote)) } /// 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, git2::Error> { +fn is_unpushed(repo: &Repository, head: &Reference, remote_name: &str) -> LibGitResult<bool> { let local_head = head.peel_to_commit()?; let remote = format!( "{}/{}", @@ -248,7 +295,7 @@ fn get_email(repository: &Repository) -> Option<String> { fn choose_remote_greedily( repository: &Repository, -) -> Result<(Option<Remote>, Option<String>), git2::Error> { +) -> LibGitResult<(Option<Remote>, Option<String>)> { let remotes = repository.remotes()?; Ok(match remotes.get(0) { Some(remote_name) => ( diff --git a/crates/gfold/src/report/target.rs b/crates/gfold/src/report/target.rs index abc836a..f9475ce 100644 --- a/crates/gfold/src/report/target.rs +++ b/crates/gfold/src/report/target.rs @@ -6,6 +6,8 @@ use std::fs::DirEntry; use std::path::PathBuf; use std::{fs, io}; +use crate::error::IoResult; + enum TargetOption { Multiple(Vec<PathBuf>), Single(PathBuf), @@ -15,7 +17,7 @@ enum TargetOption { /// 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) -> io::Result<TargetOption> { +fn process_entry(entry: &DirEntry) -> IoResult<TargetOption> { match entry.file_type()?.is_dir() && !entry .file_name() @@ -40,7 +42,7 @@ fn process_entry(entry: &DirEntry) -> io::Result<TargetOption> { /// 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>> { +pub fn generate_targets(path: PathBuf) -> IoResult<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 => { @@ -56,7 +58,7 @@ pub fn generate_targets(path: PathBuf) -> io::Result<Vec<PathBuf>> { let processed = entries .par_iter() .map(process_entry) - .collect::<Vec<io::Result<TargetOption>>>(); + .collect::<Vec<IoResult<TargetOption>>>(); let mut results = Vec::new(); for entry in processed { diff --git a/crates/gfold/src/run.rs b/crates/gfold/src/run.rs index a67b9cf..442c5e4 100644 --- a/crates/gfold/src/run.rs +++ b/crates/gfold/src/run.rs @@ -1,12 +1,13 @@ //! This module contains the execution logic for generating reports and displaying them to `stdout`. use crate::config::Config; +use crate::error::AnyhowResult; use crate::{display, report}; /// 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) -> anyhow::Result<()> { +pub fn run(config: &Config) -> AnyhowResult<()> { let reports = report::generate_reports(&config.path, &config.display_mode)?; display::display(&config.display_mode, &reports, &config.color_mode) } diff --git a/docs/DEVELOPING.md b/docs/DEVELOPING.md index d4ed78e..8402799 100644 --- a/docs/DEVELOPING.md +++ b/docs/DEVELOPING.md @@ -10,7 +10,7 @@ First, update dependencies and tidy your changes. cargo fmt cargo update cargo fix --edition-idioms --allow-dirty --allow-staged -cargo clippy --all-features --all-targets +cargo clippy --all-features --all-targets --no-deps ``` Now, ensure that lints, tests, and builds succeed. @@ -18,7 +18,7 @@ Now, ensure that lints, tests, and builds succeed. ```shell cargo fmt --all -- --check cargo clippy -- -D warnings -RUSTDOCFLAGS="-Dwarnings" cargo doc --all +RUSTDOCFLAGS="-Dwarnings" cargo doc --all --no-deps cargo test -- --nocapture cargo build --all-targets ``` diff --git a/docs/MANUAL_INSTALL.md b/docs/MANUAL_INSTALL.md index 02e7473..d89a074 100644 --- a/docs/MANUAL_INSTALL.md +++ b/docs/MANUAL_INSTALL.md @@ -78,12 +78,16 @@ rm /usr/local/bin/gfold rm $HOME/.config/gfold.toml ``` -## Build From Source Locally with Cargo +## Build From Source Locally On All Platforms If you want to install from source locally, and not from [crates.io](https://crates.io/crates/gfold), you can clone the repository and build `gfold`. This should work on all major platforms. -```shell +```bash git clone https://github.com/nickgerace/gfold.git -cargo install --path gfold -```
\ No newline at end of file +cd gfold; cargo install --path crates/gfold +``` + +The commands above were tested on macOS. +Slight modification may be required for your platform, but the flow should be the same: clone, change directory and run +`cargo install`.
\ No newline at end of file diff --git a/docs/RELEASE.md b/docs/RELEASE.md index b46ea97..4b87ee0 100644 --- a/docs/RELEASE.md +++ b/docs/RELEASE.md @@ -16,8 +16,8 @@ Steps should be executed in sequential order. cargo fmt --all -- --check cargo clippy -- -D warnings cargo test -RUSTDOCFLAGS="-Dwarnings" cargo doc --all -cargo build +RUSTDOCFLAGS="-Dwarnings" cargo doc --all --no-deps +cargo build --all-targets ``` - [ ] Create and _do not merge_ a commit with the following message: `Update to <tag>` |