summary_content = { (!NEWLINE ~ ANY)+ }
message = { SOI ~ summary ~ (blank_line* ~ (footers | (body ~ blank_line+ ~ footers) | body))? ~ EOI }
// <type>[optional scope]: <description>
summary = ${ commit_type ~ scope? ~ breaking_change_mark? ~ type_separator ~ whitespace_terminal ~ summary_content }
commit_type = { ASCII_ALPHA+ }
scope = { (parent_left ~ scope_content ~ parent_right) }
breaking_change_mark = { "!"? }
scope_content = ${ ( no_whitespace ~ no_parenthesis ~ !NEWLINE ~ ANY)+ }
// [optional body]
body = { (!(blank_line+ ~ footer) ~ ANY)+ }
// [optional footer(s)]
footers = { (footer ~ NEWLINE)* ~ footer }
footer = ${ token ~ token_separator ~ footer_content }
breaking_change_token = { "BREAKING CHANGE" | "BREAKING-CHANGE" }
lower_case_breaking_change_token = { "breaking change" | "breaking-change" }
token = ${ breaking_change_token | (!token_separator ~ ( ASCII_ALPHANUMERIC | "-"))+ }
token_separator = { ":" ~ (" " | NEWLINE) | " #" }
footer_content = { (!(NEWLINE ~ footer) ~ ANY)+ }
type_separator = { ":" }
whitespace_terminal = { " " }
parent_left = _{ "(" }
parent_right = _{ ")" }
WHITESPACE = _{ " " }
blank_line = _{ NEWLINE ~ WHITESPACE* }
// Errors
no_parenthesis = { !(parent_right | parent_left) }
no_whitespace = ${!WHITE_SPACE}