Skip to content

Commit

Permalink
Add display mode for standard alphabetical output
Browse files Browse the repository at this point in the history
This commit adds a new display mode called "standard-alphabetical" that
displays results in the "standard" format, but with solely an
alphabetical sort.

This commit also bumps dependencies across the board and adds the
"remain" crate to help sort enum variants.

Signed-off-by: Nick Gerace <[email protected]>
  • Loading branch information
nickgerace committed May 23, 2024
1 parent ee273bc commit 89e88b8
Show file tree
Hide file tree
Showing 17 changed files with 315 additions and 268 deletions.
432 changes: 211 additions & 221 deletions Cargo.lock

Large diffs are not rendered by default.

18 changes: 13 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ members = ["bin/gfold", "bin/xtask", "lib/libgfold"]
default-members = ["bin/gfold"]
resolver = "2"

[workspace.package]
authors = ["Nick Gerace <[email protected]>"]
edition = "2021"
homepage = "https://github.com/nickgerace/gfold"
license = "Apache-2.0"
repository = "https://github.com/nickgerace/gfold"

[profile.release.package.gfold]
codegen-units = 1
opt-level = 3
Expand All @@ -14,17 +21,18 @@ panic = "abort"

[workspace.dependencies]
anyhow = { version = "1.0", features = ["backtrace"] }
clap = { version = "4.4", features = ["derive"] }
clap = { version = "4.5", features = ["derive"] }
dirs = "5.0"
env_logger = { version = "0.10", features = ["humantime"], default_features = false }
env_logger = { version = "0.11", features = ["humantime"], default_features = false }
git2 = { version = "0.18", default_features = false }
log = "0.4"
pretty_assertions = "1.4"
rayon = "1.8"
rayon = "1.10"
remain = "0.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
strum = { version = "0.25", features = ["derive"] }
tempfile = "3.8"
strum = { version = "0.26", features = ["derive"] }
tempfile = "3.10"
termcolor = "1.4"
thiserror = "1.0"
toml = "0.8"
12 changes: 7 additions & 5 deletions bin/gfold/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
[package]
name = "gfold"
version = "4.4.1"
edition = "2021"

authors = ["Nick Gerace <[email protected]>"]
categories = ["command-line-utilities", "command-line-interface"]
description = "CLI tool to help keep track of your Git repositories."
homepage = "https://nickgerace.dev"
keywords = ["git", "cli"]
license = "Apache-2.0"
readme = "../../README.md"
repository = "https://github.com/nickgerace/gfold/"

authors.workspace = true
edition.workspace = true
homepage.workspace = true
license.workspace = true
repository.workspace = true

[dependencies]
libgfold = { version = "0.1.2", path = "../../lib/libgfold" }
Expand All @@ -20,6 +21,7 @@ clap = { workspace = true }
dirs = { workspace = true }
env_logger = { workspace = true }
log = { workspace = true }
remain = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
termcolor = { workspace = true }
Expand Down
19 changes: 11 additions & 8 deletions bin/gfold/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Troubleshooting:
\"RUST_BACKTRACE=1\"and \"RUST_LOG=debug\". You can adjust those variable's
values to aid investigation.";

#[remain::sorted]
#[derive(Error, Debug)]
pub enum CliError {
#[error("invalid color mode provided (exec \"--help\" for options): {0}")]
Expand All @@ -54,7 +55,7 @@ struct Cli {
#[arg(
short,
long,
help = "specify display format (options: [\"standard\", \"default\", \"json\", \"classic\"])"
help = "specify display format (options: [\"standard\", \"standard-alphabetical\", \"json\", \"classic\", \"default\"])"
)]
display_mode: Option<String>,
#[arg(
Expand Down Expand Up @@ -86,13 +87,14 @@ impl CliHarness {
};
debug!("loaded initial config");

if let Some(found_display_mode) = &self.cli.display_mode {
config.display_mode = match found_display_mode.to_lowercase().as_str() {
"classic" => DisplayMode::Classic,
"json" => DisplayMode::Json,
"standard" | "default" => DisplayMode::Standard,
_ => {
return Err(CliError::InvalidDisplayMode(found_display_mode.to_string()).into())
if let Some(found_display_mode_raw) = &self.cli.display_mode {
config.display_mode = match DisplayMode::from_str(found_display_mode_raw.to_lowercase())
{
Some(found_display_mode) => found_display_mode,
None => {
return Err(
CliError::InvalidDisplayMode(found_display_mode_raw.to_string()).into(),
);
}
}
}
Expand All @@ -118,6 +120,7 @@ impl CliHarness {
DisplayMode::Classic => (false, false),
DisplayMode::Json => (true, true),
DisplayMode::Standard => (true, false),
DisplayMode::StandardAlphabetical => (true, false),
};
let repository_collection =
RepositoryCollector::run(&config.path, include_email, include_submodules)?;
Expand Down
23 changes: 21 additions & 2 deletions bin/gfold/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::path::PathBuf;
use std::{env, fs, io};
use thiserror::Error;

#[remain::sorted]
#[derive(Error, Debug)]
pub enum ConfigError {
#[error("could not find home directory")]
Expand Down Expand Up @@ -99,17 +100,35 @@ struct EntryConfig {
/// less information to be displayed, then some commands and functions might get skipped.
/// In summary, while this setting is primarily for cosmetics, it may also affect runtime
/// performance based on what needs to be displayed.
#[remain::sorted]
#[derive(Serialize, Deserialize, Clone, Copy)]
pub enum DisplayMode {
/// Informs the caller to display results in the standard (default) format.
Standard,
/// Informs the caller to display results in the classic format.
Classic,
/// Informs the caller to display results in JSON format.
Json,
/// Informs the caller to display results in the standard (default) format. All results are
/// sorted alphabetically and then sorted by status.
Standard,
/// Informs the caller to display results in the standard (default) format with a twist: all
/// results are solely sorted alphabetically (i.e. no additional sort by status).
StandardAlphabetical,
}

impl DisplayMode {
pub fn from_str(input: impl AsRef<str>) -> Option<Self> {
match input.as_ref() {
"classic" => Some(Self::Classic),
"json" => Some(Self::Json),
"standard" | "default" => Some(Self::Standard),
"standard-alphabetical" => Some(Self::StandardAlphabetical),
_ => None,
}
}
}

/// Set the color mode of results printed to `stdout`.
#[remain::sorted]
#[derive(Serialize, Deserialize, Clone, Copy)]
pub enum ColorMode {
/// Attempt to display colors as intended (default behavior).
Expand Down
15 changes: 12 additions & 3 deletions bin/gfold/src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ mod color;
const PAD: usize = 2;
const NONE: &str = "none";

#[remain::sorted]
#[derive(Error, Debug)]
pub enum DisplayError {
#[error("could not convert path (Path) to &str: {0}")]
Expand All @@ -38,22 +39,30 @@ impl DisplayHarness {
/// This function chooses the display execution function based on the [`DisplayMode`] provided.
pub fn run(&self, reports: &RepositoryCollection) -> anyhow::Result<()> {
match self.display_mode {
DisplayMode::Standard => Self::standard(reports, self.color_mode)?,
DisplayMode::Standard => Self::standard(reports, self.color_mode, false)?,
DisplayMode::StandardAlphabetical => Self::standard(reports, self.color_mode, true)?,
DisplayMode::Json => Self::json(reports)?,
DisplayMode::Classic => Self::classic(reports, self.color_mode)?,
}
Ok(())
}

/// Display [`RepositoryCollection`] to `stdout` in the standard (default) format.
fn standard(reports: &RepositoryCollection, color_mode: ColorMode) -> anyhow::Result<()> {
fn standard(
reports: &RepositoryCollection,
color_mode: ColorMode,
alphabetical_sort_only: bool,
) -> anyhow::Result<()> {
debug!("detected standard display mode");
let mut all_reports = Vec::new();
for grouped_report in reports {
all_reports.append(&mut grouped_report.1.clone());
}

all_reports.sort_by(|a, b| a.name.cmp(&b.name));
all_reports.sort_by(|a, b| a.status.as_str().cmp(b.status.as_str()));
if !alphabetical_sort_only {
all_reports.sort_by(|a, b| a.status.as_str().cmp(b.status.as_str()));
}

let color_harness = ColorHarness::new(&color_mode);

Expand Down
8 changes: 7 additions & 1 deletion bin/xtask/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
[package]
name = "xtask"
version = "0.1.0"
edition = "2021"
publish = false

authors.workspace = true
edition.workspace = true
homepage.workspace = true
license.workspace = true
repository.workspace = true

[dependencies]
clap = { workspace = true }
dirs = { workspace = true }
remain = { workspace = true }
strum = { workspace = true }
termcolor = { workspace = true }
thiserror = { workspace = true }
7 changes: 4 additions & 3 deletions bin/xtask/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,9 @@ use thiserror::Error;
pub use task::TaskHarness;
pub use task::TaskRunner;

#[remain::sorted]
#[derive(Error, Debug)]
pub enum TaskError {
#[error(transparent)]
Io(#[from] io::Error),

#[error("cargo command failed")]
CargoCommandFailed,
#[error("could not determine repository root")]
Expand All @@ -24,6 +22,8 @@ pub enum TaskError {
HomeDirectoryNotFound,
#[error("invalid task provided: {0}")]
InvalidTaskProvided(String),
#[error(transparent)]
Io(#[from] io::Error),
#[error("repository does not have parent directory (this should be impossible)")]
RepositoryDoesNotHaveParentDirectory,
#[error("command during loose bench was not successful: {0:?}")]
Expand All @@ -39,6 +39,7 @@ struct Cli {
command: Task,
}

#[remain::sorted]
#[derive(Display, Subcommand)]
#[strum(serialize_all = "kebab-case")]
pub enum Task {
Expand Down
3 changes: 2 additions & 1 deletion bin/xtask/src/task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ pub trait TaskRunner {
fn run(harness: &mut TaskHarness) -> TaskResult<()>;
}

#[remain::sorted]
enum WriteKind {
Stdout,
Stderr,
Stdout,
}

impl TaskHarness {
Expand Down
File renamed without changes.
12 changes: 7 additions & 5 deletions lib/libgfold/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
[package]
name = "libgfold"
version = "0.1.2"
edition = "2021"

authors = ["Nick Gerace <[email protected]>"]
categories = ["development-tools"]
description = "Provides the ability to find a minimal set of user-relevant information for Git repositories on a local filesystem."
homepage = "https://nickgerace.dev"
keywords = ["git"]
license = "Apache-2.0"
readme = "README.md"
repository = "https://github.com/nickgerace/gfold/"

authors.workspace = true
edition.workspace = true
homepage.workspace = true
license.workspace = true
repository.workspace = true

[dependencies]
git2 = { workspace = true }
log = { workspace = true }
rayon = { workspace = true }
remain = { workspace = true }
serde = { workspace = true }
thiserror = { workspace = true }

Expand Down
1 change: 1 addition & 0 deletions lib/libgfold/src/collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::repository_view::{RepositoryView, RepositoryViewError, RepositoryView
mod target;

#[allow(missing_docs)]
#[remain::sorted]
#[derive(Error, Debug)]
pub enum CollectorError {
#[error(transparent)]
Expand Down
5 changes: 3 additions & 2 deletions lib/libgfold/src/collector/target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,12 @@ impl TargetCollector {
}

/// An enum that contains 0 to N targets based on the variant.
#[remain::sorted]
enum MaybeTarget {
/// Contains multiple targets from recursive call(s) of [`TargetCollector::run()`].
Multiple(Vec<PathBuf>),
/// Contains a single target.
Single(PathBuf),
/// Does not contain a target.
None,
/// Contains a single target.
Single(PathBuf),
}
4 changes: 2 additions & 2 deletions lib/libgfold/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ mod tests {
use std::{fs, io};
use tempfile::tempdir;

/// This integration test for `gfold` covers an end-to-end usage scenario. It uses the
/// This scenario test for `gfold` covers an end-to-end usage scenario. It uses the
/// [`tempfile`](tempfile) crate to create some repositories with varying states and levels
/// of nesting.
#[test]
fn integration() -> anyhow::Result<()> {
fn scenario() -> anyhow::Result<()> {
env_logger::builder()
.is_test(true)
.filter_level(LevelFilter::Info)
Expand Down
13 changes: 7 additions & 6 deletions lib/libgfold/src/repository_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,21 @@ use crate::status::{Status, StatusError};
mod submodule_view;

#[allow(missing_docs)]
#[remain::sorted]
#[derive(Error, Debug)]
pub enum RepositoryViewError {
#[error("received None (Option<&OsStr>) for file name: {0}")]
FileNameNotFound(PathBuf),
#[error("could not convert file name (&OsStr) to &str: {0}")]
FileNameToStrConversionFailure(PathBuf),
#[error(transparent)]
FromGit2(#[from] git2::Error),
#[error(transparent)]
FromStatus(#[from] StatusError),
#[error(transparent)]
FromStdIo(#[from] io::Error),
#[error(transparent)]
FromSubmodule(#[from] SubmoduleError),
#[error(transparent)]
FromStatus(#[from] StatusError),
#[error("received None (Option<&OsStr>) for file name: {0}")]
FileNameNotFound(PathBuf),
#[error("could not convert file name (&OsStr) to &str: {0}")]
FileNameToStrConversionFailure(PathBuf),
#[error("full shorthand for Git reference is invalid UTF-8")]
GitReferenceShorthandInvalid,
#[error("could not convert path (Path) to &str: {0}")]
Expand Down
9 changes: 5 additions & 4 deletions lib/libgfold/src/repository_view/submodule_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,17 @@ use thiserror::Error;

use crate::status::{Status, StatusError};

#[remain::sorted]
#[derive(Error, Debug)]
pub enum SubmoduleError {
#[error("submodule name is invalid UTF-8")]
SubmoduleNameInvalid,
#[error(transparent)]
FromGit2(#[from] git2::Error),
#[error(transparent)]
FromStdIo(#[from] io::Error),
#[error(transparent)]
FromStatus(#[from] StatusError),
#[error(transparent)]
FromStdIo(#[from] io::Error),
#[error("submodule name is invalid UTF-8")]
SubmoduleNameInvalid,
}

type SubmoduleResult<T> = Result<T, SubmoduleError>;
Expand Down
2 changes: 2 additions & 0 deletions lib/libgfold/src/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize};
use thiserror::Error;

#[allow(missing_docs)]
#[remain::sorted]
#[derive(Error, Debug)]
pub enum StatusError {
#[error(transparent)]
Expand All @@ -16,6 +17,7 @@ pub enum StatusError {
pub type StatusResult<T> = Result<T, StatusError>;

/// A summarized interpretation of the status of a Git working tree.
#[remain::sorted]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum Status {
/// Corresponds to a "bare" working tree.
Expand Down

0 comments on commit 89e88b8

Please sign in to comment.