diff --git a/Cargo.lock b/Cargo.lock index a603ad4..ff2e73d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -102,6 +102,12 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "bytecount" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205" + [[package]] name = "cfg-if" version = "1.0.0" @@ -148,7 +154,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] [[package]] @@ -310,6 +316,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "getrandom" version = "0.2.12" @@ -352,6 +364,7 @@ dependencies = [ "serde_json", "strum", "strum_macros", + "tabled", "tempfile", "thiserror", "toml", @@ -479,6 +492,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "papergrid" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ad43c07024ef767f9160710b3a6773976194758c7919b17e63b863db0bdf7fb" +dependencies = [ + "bytecount", + "fnv", + "unicode-width", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -539,6 +563,30 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.78" @@ -684,7 +732,7 @@ checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] [[package]] @@ -729,7 +777,18 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn", + "syn 2.0.48", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] @@ -743,6 +802,30 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tabled" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c998b0c8b921495196a48aabaf1901ff28be0760136e31604f7967b0792050e" +dependencies = [ + "papergrid", + "tabled_derive", + "unicode-width", +] + +[[package]] +name = "tabled_derive" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c138f99377e5d653a371cdad263615634cfc8467685dfe8e73e2b8e98f44b17" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "tempfile" version = "3.9.0" @@ -790,7 +873,7 @@ checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 00df21a..656cdbb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" strum = "0.26" strum_macros = "0.26" +tabled = "0.15.0" thiserror = "1.0" toml = "0.8" diff --git a/src/args.rs b/src/args.rs index 4cbb259..d0bb820 100644 --- a/src/args.rs +++ b/src/args.rs @@ -46,7 +46,7 @@ pub struct Opt { pub display: bool, /// Specify the output format for displaying the parsed commit message. - /// Options: "table", "json", "toml". Default: "table" + /// Options: "cli", "table", "json", "toml". Default: "cli" #[arg(short = 'f', long, env = "GIT_SUMI_FORMAT")] pub format: Option, diff --git a/src/config.rs b/src/config.rs index aacd470..a3c4ad9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -42,8 +42,9 @@ pub trait Configurable { #[derive(Debug, Clone, Serialize, Deserialize, EnumIter, AsRefStr, Default)] #[serde(rename_all = "lowercase")] pub enum ParsedCommitDisplayFormat { - Json, #[default] + Cli, + Json, Table, Toml, } @@ -296,7 +297,7 @@ impl Config { let config_comments = HashMap::from([ ("quiet", "Suppress progress messages."), ("display", "Shows the parsed commit message post-linting. See 'format' for options."), - ("format", "Output format for the parsed commit message. Options: \"json\", \"table\", \"toml\"."), + ("format", "Output format for the parsed commit message. Options: \"cli\", \"json\", \"table\", \"toml\"."), ("split_lines", "Process each non-empty line in the commit message as an individual commit."), ("gitmoji", "Rule: include one valid Gitmoji: https://gitmoji.dev/"), ("conventional", "Rule: follow Conventional Commits format: https://www.conventionalcommits.org/"), diff --git a/src/lint/display.rs b/src/lint/display.rs index 0a73cb1..11c1317 100644 --- a/src/lint/display.rs +++ b/src/lint/display.rs @@ -1,16 +1,24 @@ use super::errors::SumiError; use crate::config::ParsedCommitDisplayFormat; use crate::parser::ParsedCommit; -use prettytable::{format, Cell, Row, Table}; use serde_json::Value; +use tabled::{ + settings::{object::Rows, Disable, Style}, + Table, Tabled, +}; pub fn display_parsed_commit( commit: &ParsedCommit, format: &ParsedCommitDisplayFormat, ) -> Result<(), SumiError> { match format { + ParsedCommitDisplayFormat::Cli => { + display_parsed_commit_as_table(commit, ParsedCommitDisplayFormat::Cli)? + } ParsedCommitDisplayFormat::Json => display_parsed_commit_as_json(commit)?, - ParsedCommitDisplayFormat::Table => display_parsed_commit_as_table(commit)?, + ParsedCommitDisplayFormat::Table => { + display_parsed_commit_as_table(commit, ParsedCommitDisplayFormat::Table)? + } ParsedCommitDisplayFormat::Toml => display_parsed_commit_as_toml(commit)?, } Ok(()) @@ -38,46 +46,62 @@ fn display_parsed_commit_as_json(commit: &ParsedCommit) -> Result<(), SumiError> Ok(()) } -fn display_parsed_commit_as_table(commit: &ParsedCommit) -> Result<(), SumiError> { - let mut table = Table::new(); - add_row_for_vector(&mut table, "Gitmoji", &commit.gitmoji); - add_row_for_string(&mut table, "Commit type", &commit.commit_type); - add_row_for_string(&mut table, "Scope", &commit.scope); - add_row_for_string(&mut table, "Description", &Some(commit.description.clone())); - add_row_for_string(&mut table, "Body", &commit.body); - add_row_for_vector(&mut table, "Footers", &commit.footers); - add_row_for_bool(&mut table, "Is breaking", &commit.is_breaking); - add_row_for_string( - &mut table, - "Breaking description", - &commit.breaking_description, - ); - add_row_for_vector(&mut table, "References", &commit.references); - table.set_format(*format::consts::FORMAT_BOX_CHARS); - table.printstd(); - Ok(()) +#[derive(Tabled)] +#[tabled(rename_all = "PascalCase")] +struct CommitRow { + key: &'static str, + value: String, } -fn add_row_for_string(table: &mut Table, label: &str, value: &Option) { - if let Some(v) = value { - table.add_row(Row::new(vec![Cell::new(label), Cell::new(v)])); - } -} +fn display_parsed_commit_as_table( + commit: &ParsedCommit, + format: ParsedCommitDisplayFormat, +) -> Result<(), SumiError> { + let mut rows = Vec::new(); + let fields = [ + ("Gitmoji", commit.gitmoji.as_ref().map(|g| g.join(", "))), + ("Commit type", commit.commit_type.clone()), + ("Scope", commit.scope.clone()), + // "Description" is the only field that is guaranteed to be present. + ("Description", Some(commit.description.clone())), + ("Body", commit.body.clone()), + ("Footers", commit.footers.as_ref().map(|f| f.join(", "))), + ( + "Is breaking", + Some(format!("{}", commit.is_breaking.unwrap_or(false))), + ), + ("Breaking description", commit.breaking_description.clone()), + ( + "References", + commit.references.as_ref().map(|r| r.join(", ")), + ), + ]; -fn add_row_for_bool(table: &mut Table, label: &str, value: &Option) { - if let Some(v) = value { - let display_value = if *v { "true" } else { "false" }; - table.add_row(Row::new(vec![Cell::new(label), Cell::new(display_value)])); + for (key, value) in fields.iter() { + if let Some(val) = value { + rows.push(CommitRow { + key, + value: val.clone(), + }); + } } -} -fn add_row_for_vector(table: &mut Table, label: &str, vec: &Option>) { - if let Some(v) = vec { - if !v.is_empty() { - let joined = v.join(", "); - table.add_row(Row::new(vec![Cell::new(label), Cell::new(&joined)])); + let mut table = Table::new(rows); + match format { + ParsedCommitDisplayFormat::Cli => { + // Cute table for terminal; no header. + table.with(Style::modern()); + table.with(Disable::row(Rows::first())); } + ParsedCommitDisplayFormat::Table => { + // Markdown table with header. + table.with(Style::markdown()); + } + _ => {} } + + println!("{}", table); + Ok(()) } fn display_parsed_commit_as_toml(commit: &ParsedCommit) -> Result<(), SumiError> { diff --git a/sumi.toml b/sumi.toml index 1614b5a..5604af0 100644 --- a/sumi.toml +++ b/sumi.toml @@ -8,8 +8,8 @@ quiet = false # Shows the parsed commit message post-linting. See 'format' for options. display = true -# Output format for the parsed commit message. Options: "json", "table", "toml". -format = "table" +# Output format for the parsed commit message. Options: "cli", "json", "table", "toml". +format = "cli" # Process each non-empty line in the commit message as an individual commit. split_lines = false diff --git a/tests/lint/test_config.rs b/tests/lint/test_config.rs index 6e948db..85a0070 100644 --- a/tests/lint/test_config.rs +++ b/tests/lint/test_config.rs @@ -318,8 +318,8 @@ quiet = false # Shows the parsed commit message post-linting. See 'format' for options. display = false -# Output format for the parsed commit message. Options: "json", "table", "toml". -format = "table" +# Output format for the parsed commit message. Options: "cli", "json", "table", "toml". +format = "cli" # Process each non-empty line in the commit message as an individual commit. split_lines = false diff --git a/tests/lint/test_display.rs b/tests/lint/test_display.rs index adbf6dd..e41dc01 100644 --- a/tests/lint/test_display.rs +++ b/tests/lint/test_display.rs @@ -17,18 +17,21 @@ fn assert_table_output(cmd: &mut Command) { .stdout(contains("Footers │ BREAKING CHANGE:breaking description, Footer1:value1, Footer2:value2")) .stdout(contains("Is breaking │ true")) .stdout(contains("Breaking description │ breaking description")) - .stdout(contains("References │ #123, ce6df36")); + .stdout(contains("References │ #123, ce6df36")) + // No headers. + .stdout(contains("Key").not()) + .stdout(contains("Value").not()); } #[test] -fn success_display_format_table() { +fn success_display_format_cli() { let mut cmd = run_isolated_git_sumi(""); - cmd.arg("-dCG").arg("-f").arg("table").arg(COMMIT_MESSAGE); + cmd.arg("-dCG").arg("-f").arg("cli").arg(COMMIT_MESSAGE); assert_table_output(&mut cmd); } #[test] -fn success_display_format_table_as_default() { +fn success_display_format_cli_as_default() { let mut cmd = run_isolated_git_sumi(""); cmd.arg("-dCG").arg(COMMIT_MESSAGE); assert_table_output(&mut cmd); @@ -124,3 +127,24 @@ fn success_does_not_contain_header() { .success() .stdout(contains("header = ").not()); } + +#[test] +fn success_display_format_markdown() { + let mut cmd = run_isolated_git_sumi(""); + cmd.arg("-dCG").arg("-f").arg("table").arg(COMMIT_MESSAGE); + cmd.assert() + .success() + .stdout(contains("| Key")) + .stdout(contains("| Value")) + .stdout(contains("| Gitmoji | 🐛")) + .stdout(contains("|----------------------|----------------------------------------------------------------------|")) + .stdout(contains("| Scope | Scope")) + .stdout(contains("| Description | short description")) + .stdout(contains("| Body | Longer body description ")) + .stdout(contains( + "| Footers | BREAKING CHANGE:breaking description, Footer1:value1, Footer2:value2 |", + )) + .stdout(contains("| Is breaking | true ")) + .stdout(contains("| Breaking description | breaking description ")) + .stdout(contains("| References | #123, ce6df36 ")); +} diff --git a/website/docs/configuration.md b/website/docs/configuration.md index 9d60d3d..e4914e3 100644 --- a/website/docs/configuration.md +++ b/website/docs/configuration.md @@ -97,7 +97,7 @@ Use `--config 'none'` to use the default configuration. ### Format -- **Description**: Specifies the output format for displaying the parsed commit message. Options are "table", "json", and "toml". +- **Description**: Specifies the output format for displaying the parsed commit message. Enabling this option automatically sets `display = true`. @@ -107,9 +107,9 @@ Use `--config 'none'` to use the default configuration. - **Environment variable**: `GIT_SUMI_FORMAT` -- **Type of value**: String (options: "table", "json", "toml") +- **Type of value**: String (options: "cli", "table", "json", "toml") -- **Default value**: "table" +- **Default value**: "cli" - **Example usage**: Set `format = "json"` in `sumi.toml` for JSON formatted output, or use `git sumi --format json`. @@ -133,7 +133,26 @@ Co-authored-by: John Doe - + + +```txt +| Key | Value | +|----------------------|----------------------------------------------------------------------| +| Gitmoji | 🐛 | +| Commit type | fix | +| Scope | auth | +| Description | resolve token refresh issue | +| Body | Fixes bug introduced in ce6df36 where the authentication token would | +| | not refresh properly during a session, causing unexpected logouts. | +| Footers | Co-authored-by:John Doe | +| Is breaking | true | +| Breaking description | resolve token refresh issue | +| References | ce6df36 | +``` + + + + ```txt ┌──────────────────────┬──────────────────────────────────────────────────────────────────────┐