conventional_commits_types/lib.rs
1//! Defines structures that can be used to work with conventional commits.
2//! The implementation resembles the v1.0.0 specification defined over at [conventionalcommits.org](https://www.conventionalcommits.org/en/v1.0.0/#specification).
3
4#[cfg(feature = "serde")]
5use serde::{Deserialize, Serialize};
6use std::{fmt, str::FromStr};
7
8/// The `:<space>` separator.
9pub const SEPARATOR_COLON: &str = ": ";
10
11/// The `<space>#` separator for footer notes.
12///
13/// This type of separator is mostly used when using issues or PR numbers as
14/// value.
15pub const SEPARATOR_HASHTAG: &str = " #";
16
17/// A commit message.
18///
19/// As per the specification, a commit message is made out of a mandatory
20/// description, an optional body and `0..n` optional footers. The different
21/// sections are separated by an empty newline. Footers can be each separated
22/// with a newline, however, this is not needed.
23///
24/// # Example
25///
26/// ```text
27/// feat(some scope): a short and concise description
28///
29/// This is a longer body message. It can wrapped around
30/// and be put onto multiple lines.
31///
32/// This is still part of the body.
33///
34/// Fixes #123
35/// PR-close #124
36/// Signed-off-by: SirWindfield
37/// ```
38#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
39#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
40pub struct Commit<'a> {
41 /// The optional body.
42 pub body: Option<&'a str>,
43 /// The mandatory description.
44 pub desc: &'a str,
45 /// A list of footers. Empty when none are part of the commit message.
46 pub footer: Vec<Footer<'a>>,
47 /// Set if the commit is a breaking change.
48 pub is_breaking_change: bool,
49 /// The optional scope.
50 pub scope: Option<&'a str>,
51 /// The mandatory type.
52 ///
53 /// Types other than `feat` and `fix` are optional. For more information, please take a look at the [specification](https://www.conventionalcommits.org/en/v1.0.0/#specification), paragraphs 1-3.
54 pub ty: &'a str,
55}
56
57impl<'a> Commit<'a> {
58 /// Creates a default commit.
59 pub fn new() -> Self {
60 Self::default()
61 }
62
63 /// Creates a commit with the given values.
64 pub fn from(
65 ty: &'a str,
66 scope: Option<&'a str>,
67 desc: &'a str,
68 body: Option<&'a str>,
69 is_breaking_change: bool,
70 footer: Vec<Footer<'a>>,
71 ) -> Self {
72 Self {
73 ty,
74 scope,
75 desc,
76 body,
77 is_breaking_change,
78 footer,
79 }
80 }
81}
82
83/// A commit message footer.
84#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
85#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
86pub struct Footer<'a> {
87 /// The footer word token.
88 pub token: &'a str,
89 /// The separator.
90 pub separator: FooterSeparator,
91 /// The footer's value.
92 pub value: &'a str,
93}
94
95impl<'a> Footer<'a> {
96 /// Creates a default footer.
97 pub fn new() -> Self {
98 Self::default()
99 }
100
101 /// Creates a footer with the given values.
102 pub fn from(token: &'a str, separator: FooterSeparator, value: &'a str) -> Self {
103 Self {
104 token,
105 separator,
106 value,
107 }
108 }
109}
110
111/// The separator used to separate the token and value of a footer.
112#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
113#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
114pub enum FooterSeparator {
115 /// The `:<space>` separator is mostly used for values that do not involve
116 /// issue or PR references.
117 ColonSpace,
118 /// The `#<space>` separator is, more often than not, used for issue and PR
119 /// references.
120 SpaceHashTag,
121}
122
123impl Default for FooterSeparator {
124 /// Returns the default FooterSeparator, the ColonSpace.
125 fn default() -> Self {
126 FooterSeparator::ColonSpace
127 }
128}
129
130impl fmt::Display for FooterSeparator {
131 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132 match self {
133 FooterSeparator::ColonSpace => write!(f, "{}", SEPARATOR_COLON),
134 FooterSeparator::SpaceHashTag => write!(f, "{}", SEPARATOR_HASHTAG),
135 }
136 }
137}
138
139impl FromStr for FooterSeparator {
140 type Err = String;
141
142 fn from_str(s: &str) -> Result<Self, Self::Err> {
143 match s {
144 SEPARATOR_COLON => Ok(FooterSeparator::ColonSpace),
145 SEPARATOR_HASHTAG => Ok(FooterSeparator::SpaceHashTag),
146 _ => Err("footer separator not recognized".to_string()),
147 }
148 }
149}