1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
//! # Conventional commit parser
//!
//! A rust implementation of the [conventional commit specification](https://www.conventionalcommits.org/en/v1.0.0/).
//! This crate expose functions to parse conventional commit messages.
//!
//! ## Example :
//! ```
//! # use conventional_commit_parser::error::ParseError;
//! # fn main() -> Result<(), ParseError> {
//!
//! use conventional_commit_parser::parse;
//! use conventional_commit_parser::commit::*;
//! let message = r#"fix: correct minor typos in code
//!
//! see the issue for details
//!
//! on typos fixed.
//!
//! Reviewed-by: Z
//! Refs #133"#;
//!
//! let conventional_commit = parse(message)?;
//!
//! assert_eq!(conventional_commit.commit_type, CommitType::BugFix);
//! assert_eq!(conventional_commit.summary, "correct minor typos in code".to_string());
//! assert_eq!(conventional_commit.body, Some(r#"see the issue for details
//!
//! on typos fixed."#.to_string()));
//!
//! assert_eq!(conventional_commit.footers, vec![
//! Footer {token: "Reviewed-by".to_string(), content: "Z".to_string()},
//! Footer {token: "Refs".to_string(), content: "133".to_string(),}
//! ]);
//!
//! # Ok(())
//! # }
//! ```
//!
#[macro_use]
extern crate pest_derive;
#[cfg(test)]
#[macro_use]
extern crate spectral;
use pest::Parser;
use crate::commit::{ConventionalCommit, Footer};
use crate::error::ParseError;
/// Conventional commit representation, produced by the [parse] function
///
/// [parse]: crate::ConventionalCommitParser::parse
pub mod commit;
pub mod error;
#[doc(hidden)]
#[derive(Parser)]
#[grammar = "grammar.pest"]
struct ConventionalCommitParser;
/// Parse a commit message into a [`commit::ConventionalCommit`]
pub fn parse(commit_message: &str) -> Result<ConventionalCommit, ParseError> {
let pairs = ConventionalCommitParser::parse(Rule::message, commit_message)
.map_err(ParseError::from)?
.next()
.unwrap();
let mut commit = ConventionalCommit::default();
for pair in pairs.into_inner() {
match pair.as_rule() {
Rule::summary => commit.set_summary(pair),
Rule::body => commit.set_commit_body(pair),
Rule::footers => commit.set_footers(pair),
_ => (),
}
}
Ok(commit)
}
/// Parse a commit summary of the following form : `<type>[optional scope]: <description>`
/// Returns a [`ConventionalCommit`] struct with a `None` body and empty footers.
///
/// # Example :
/// ```
/// # use conventional_commit_parser::error::ParseError;
/// # fn main() -> Result<(), ParseError> {
///
/// use conventional_commit_parser::parse_summary;
/// use conventional_commit_parser::commit::*;
///
/// let message = "feat(parser): implement parse_summary";
///
/// let parsed = parse_summary(message).expect("Parse error");
///
/// assert_eq!(parsed, ConventionalCommit {
/// commit_type: CommitType::Feature,
/// scope: Some("parser".to_string()),
/// summary: "implement parse_summary".to_string(),
/// body: None,
/// footers: vec![],
/// is_breaking_change: false
/// });
/// # Ok(())
/// # }
pub fn parse_summary(summary: &str) -> Result<ConventionalCommit, ParseError> {
let pair = ConventionalCommitParser::parse(Rule::summary, summary)
.map_err(ParseError::from)?
.next()
.unwrap();
let mut commit = ConventionalCommit::default();
commit.set_summary(pair);
Ok(commit)
}
/// Parse a commit body only returning an `Option<String>` on a non empty trimmed value
///
/// # Example :
/// ```
/// # use conventional_commit_parser::error::ParseError;
/// # fn main() -> Result<(), ParseError> {
///
/// use conventional_commit_parser::parse_body;
/// use conventional_commit_parser::commit::*;
///
/// let body = r#"resolves [#10]
/// will be merged in the next release"#;
///
/// let parsed = parse_body(body).expect("Parse error");
///
/// assert_eq!(parsed, Some(body.to_string()));
/// # Ok(())
/// # }
pub fn parse_body(body: &str) -> Result<Option<String>, ParseError> {
let pair = ConventionalCommitParser::parse(Rule::body, body)
.map_err(ParseError::from)?
.next()
.unwrap();
let body = pair.as_str();
if !body.is_empty() {
Ok(Some(body.to_string()))
} else {
Ok(None)
}
}
/// Parse commit footers only
///
/// # Example :
/// ```
/// # use conventional_commit_parser::error::ParseError;
/// # fn main() -> Result<(), ParseError> {
///
/// use conventional_commit_parser::parse_footers;
/// use conventional_commit_parser::commit::*;
///
/// let footer = r#"a-token: this is a token
/// another-token #this is a token with hash separator"#;
///
/// let parsed = parse_footers(footer).expect("Parse error");
///
/// assert_eq!(parsed, vec![
/// Footer { token: "a-token".to_string(), content: "this is a token".to_string() },
/// Footer { token: "another-token".to_string(), content: "this is a token with hash separator".to_string() }
/// ]);
/// # Ok(())
/// # }
pub fn parse_footers(footers: &str) -> Result<Vec<Footer>, ParseError> {
let pair = ConventionalCommitParser::parse(Rule::footers, footers)
.map_err(ParseError::from)?
.next()
.unwrap();
let mut footers = vec![];
for pair in pair.into_inner() {
footers.push(Footer::from(pair));
}
Ok(footers)
}