Skip to content

Commit

Permalink
refactor: rework check command output and commit parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
oknozor committed Sep 10, 2020
1 parent d7508af commit d9cf446
Show file tree
Hide file tree
Showing 6 changed files with 324 additions and 50 deletions.
28 changes: 16 additions & 12 deletions src/bin/cog.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use anyhow::Result;
use clap::{App, AppSettings, Arg, SubCommand};
use cocogitto::CocoGitto;
use cocogitto::{CocoGitto, VersionIncrement};
use std::process::exit;

const APP_SETTINGS: &[AppSettings] = &[
Expand All @@ -16,7 +16,7 @@ const SUBCOMMAND_SETTINGS: &[AppSettings] = &[
AppSettings::VersionlessSubcommands,
];

const PUBLISH: &str = "publish";
const BUMP: &str = "bump";
const CHECK: &str = "check";
const VERIFY: &str = "verify";
const CHANGELOG: &str = "changelog";
Expand Down Expand Up @@ -58,7 +58,7 @@ fn main() -> Result<()> {
.arg(Arg::with_name("message").help("The commit message"))
)
.subcommand(
SubCommand::with_name(PUBLISH)
SubCommand::with_name(BUMP)
.settings(SUBCOMMAND_SETTINGS)
.about("Commit changelog from latest tag to HEAD and create a new tag")
.arg(Arg::with_name("version")
Expand Down Expand Up @@ -111,20 +111,24 @@ fn main() -> Result<()> {

if let Some(subcommand) = matches.subcommand_name() {
match subcommand {
PUBLISH => {
let subcommand = matches.subcommand_matches(PUBLISH).unwrap();
BUMP => {
let subcommand = matches.subcommand_matches(BUMP).unwrap();

if let Some(version) = subcommand.value_of("version") {
todo!()
let increment = if let Some(version) = subcommand.value_of("version") {
VersionIncrement::Manual(version.to_string())
} else if subcommand.is_present("auto") {
todo!()
VersionIncrement::Auto
} else if subcommand.is_present("major") {
todo!()
VersionIncrement::Major
} else if subcommand.is_present("patch") {
todo!()
VersionIncrement::Patch
} else if subcommand.is_present("minor") {
todo!()
}
VersionIncrement::Minor
} else {
unreachable!()
};

cocogitto.create_version(increment)?
}
VERIFY => {
let subcommand = matches.subcommand_matches(VERIFY).unwrap();
Expand Down
120 changes: 95 additions & 25 deletions src/commit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ pub struct Commit {
pub struct CommitMessage {
pub(crate) commit_type: CommitType,
pub(crate) scope: Option<String>,
pub(crate) body: Option<String>,
pub(crate) footer: Option<String>,
pub(crate) description: String,
pub(crate) is_breaking_change: bool,
}

#[derive(Debug, Deserialize)]
Expand All @@ -42,39 +45,75 @@ impl Commit {
let commit = commit.to_owned();
let message = commit.message();
let message = message.unwrap().to_owned();

// TODO : lint
let message_display = message.replace("\n", " ");
let message_display = if message_display.len() > 80 {
message_display[0..80].blue()
} else {
message_display.blue()
};

println!("Parsing commit : {} - {}", shorthand, message_display);

let author = commit.author().name().unwrap_or_else(|| "").to_string();
let message = Commit::parse_commit_message(&message)?;

Ok(Commit {
let commit = Commit {
shorthand,
message,
author,
})
};

Ok(commit)
}

pub(crate) fn parse_commit_message(message: &str) -> Result<CommitMessage> {
let split: Vec<&str> = message.split(": ").to_owned().collect();
let type_separator = message.find(": ");
if type_separator.is_none() {
return Err(anyhow!(
"{} : invalid commit format : missing {} separator",
"Error".red(),
": ".yellow()
));
}

let idx = type_separator.unwrap();

let mut type_and_scope = &message[0..idx];
let mut is_breaking_change = type_and_scope.chars().last() == Some('!');

if is_breaking_change {
type_and_scope = &type_and_scope[0..type_and_scope.len() - 1];
}

let mut commit_type;

let scope: Option<String> = if let Some(left_par_idx) = type_and_scope.find("(") {
commit_type = CommitType::from(&type_and_scope[0..left_par_idx]);

if split.len() <= 1 {
return Err(anyhow!("{} : invalid commit format", "Error".red()));
Some(
type_and_scope
.find(")")
.ok_or(anyhow!("{} : missing closing parenthesis", "Error".red()))
.map(|right_par_idx| {
type_and_scope[left_par_idx + 1..right_par_idx].to_string()
})?,
)
} else {
commit_type = CommitType::from(type_and_scope);
None
};

let contents = &message[idx + 2..message.len()];
let contents: Vec<&str> = contents.split('\n').collect();

let description = contents.get(0).map(|desc| desc.to_string());

if description.is_none() {
return Err(anyhow!("{} : missing commit description", "Error".red()));
}

let description = split[1].to_owned().replace('\n', " ");
let description = description.unwrap();

let left_part: Vec<&str> = split[0].split("(").collect();
let body = contents.get(1).map(|desc| desc.to_string());

let commit_type = CommitType::from(left_part[0]);
let footer = contents.get(2).map(|desc| desc.to_string());

if let Some(footer) = &footer {
is_breaking_change = is_breaking_change
|| footer.contains("BREAKING CHANGE")
|| footer.contains("BREAKING-CHANGE")
}

if let CommitType::Unknown(type_str) = commit_type {
return Err(anyhow!(
Expand All @@ -84,14 +123,13 @@ impl Commit {
));
};

let scope = left_part
.get(1)
.map(|scope| scope[0..scope.len() - 1].to_owned());

Ok(CommitMessage {
description,
commit_type,
scope,
body,
footer,
is_breaking_change,
})
}

Expand All @@ -105,6 +143,38 @@ impl Commit {
}
}

impl fmt::Display for Commit {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let message_display = self.message.description.replace("\n", " ");
let message_display = if message_display.len() > 80 {
format!("{}{}", &message_display[0..80], "...").blue()
} else {
message_display.blue()
};

let type_format = "type:".green().bold();
let scope_format = "scope:".green().bold();
let message_format = "message:".green().bold();
let breaking_change = if self.message.is_breaking_change {
format!(" - {}", "BREAKING CHANGE".red().bold())
} else {
"".to_string()
};
write!(
f,
"{}{}\n\t{} {}\n\t{} {}\n\t{} {}\n",
&self.shorthand.bold(),
breaking_change,
type_format,
&self.message.commit_type,
scope_format,
&self.message.scope.as_ref().unwrap_or(&"none".to_string()),
message_format,
message_display
)
}
}

#[derive(Eq, PartialEq, Debug)]
pub(crate) enum CommitType {
Feature,
Expand Down Expand Up @@ -141,7 +211,7 @@ impl CommitType {
}
}

fn get_key_string(&self) -> String {
pub(crate) fn get_key_str(&self) -> String {
match &self {
Feature => "feat".to_string(),
BugFix => "fix".to_string(),
Expand Down Expand Up @@ -181,7 +251,7 @@ impl From<&str> for CommitType {

impl fmt::Display for CommitType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.get_key_string())
write!(f, "{}", self.get_key_str())
}
}

Expand Down
56 changes: 56 additions & 0 deletions src/hook.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use anyhow::Result;
use colored::*;
use std::process::{Command, Stdio};

#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Hook {
pub command: String,
}

impl Hook {
pub(crate) fn run(&self) -> Result<()> {
let command_display = format!("`{}`", &self.command.green());
println!("Running pre-version hook : {}", command_display);

Command::new(self.command.clone())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.stdin(Stdio::inherit())
.output()
.map_err(|err| anyhow!(err))
.map(|_| ())
}
}

#[cfg(test)]
mod tests {
use crate::hook::Hook;

#[test]
fn should_run_command() {
// Arrange
let hook = Hook {
command: "echo hello world".to_string(),
};

// Act
let result = hook.run();

// Assert
assert!(result.is_ok());
}

#[test]
fn should_fail_to_run_invalid_command() {
// Arrange
let hook = Hook {
command: "azmroih".to_string(),
};

// Act
let result = hook.run();

// Assert
assert!(result.is_err());
}
}
Loading

0 comments on commit d9cf446

Please sign in to comment.