Skip to main content

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}