mit_commit/
comments.rs

1use std::slice::Iter;
2
3use crate::{comment::Comment, fragment::Fragment};
4
5/// A collection of comments from a [`CommitMessage`]
6#[derive(Debug, PartialEq, Eq, Clone, Default)]
7pub struct Comments<'a> {
8    comments: Vec<Comment<'a>>,
9}
10
11impl Comments<'_> {
12    /// Iterate over the [`Comment`] in the [`Comments`]
13    ///
14    /// # Returns
15    ///
16    /// An iterator over the comments in this collection
17    ///
18    /// # Examples
19    ///
20    /// ```
21    /// use mit_commit::{Comment, Comments};
22    ///
23    /// let trailers = Comments::from(vec![
24    ///     Comment::from("# Comment 1"),
25    ///     Comment::from("# Comment 2"),
26    ///     Comment::from("# Comment 3"),
27    /// ]);
28    /// let mut iterator = trailers.iter();
29    ///
30    /// assert_eq!(iterator.next(), Some(&Comment::from("# Comment 1")));
31    /// assert_eq!(iterator.next(), Some(&Comment::from("# Comment 2")));
32    /// assert_eq!(iterator.next(), Some(&Comment::from("# Comment 3")));
33    /// assert_eq!(iterator.next(), None);
34    /// ```
35    pub fn iter(&self) -> Iter<'_, Comment<'_>> {
36        self.comments.iter()
37    }
38}
39
40impl<'a> IntoIterator for Comments<'a> {
41    type IntoIter = std::vec::IntoIter<Comment<'a>>;
42    type Item = Comment<'a>;
43
44    /// Iterate over the [`Comment`] in the [`Comments`]
45    ///
46    /// # Returns
47    ///
48    /// An iterator that takes ownership of the comments
49    ///
50    /// # Examples
51    ///
52    /// ```
53    /// use mit_commit::{Comment, Comments};
54    ///
55    /// let trailers = Comments::from(vec![
56    ///     Comment::from("# Comment 1"),
57    ///     Comment::from("# Comment 2"),
58    ///     Comment::from("# Comment 3"),
59    /// ]);
60    /// let mut iterator = trailers.into_iter();
61    ///
62    /// assert_eq!(iterator.next(), Some(Comment::from("# Comment 1")));
63    /// assert_eq!(iterator.next(), Some(Comment::from("# Comment 2")));
64    /// assert_eq!(iterator.next(), Some(Comment::from("# Comment 3")));
65    /// assert_eq!(iterator.next(), None);
66    /// ```
67    fn into_iter(self) -> Self::IntoIter {
68        self.comments.into_iter()
69    }
70}
71
72impl<'a> IntoIterator for &'a Comments<'a> {
73    type IntoIter = Iter<'a, Comment<'a>>;
74    type Item = &'a Comment<'a>;
75
76    /// Iterate over the [`Comment`] in the [`Comments`]
77    ///
78    /// # Returns
79    ///
80    /// An iterator over references to the comments
81    ///
82    /// # Examples
83    ///
84    /// ```
85    /// use std::borrow::Borrow;
86    ///
87    /// use mit_commit::{Comment, Comments};
88    ///
89    /// let comments = Comments::from(vec![
90    ///     Comment::from("# Comment 1"),
91    ///     Comment::from("# Comment 2"),
92    ///     Comment::from("# Comment 3"),
93    /// ]);
94    /// let comments_ref = comments.borrow();
95    /// let mut iterator = comments_ref.into_iter();
96    ///
97    /// assert_eq!(iterator.next(), Some(&Comment::from("# Comment 1")));
98    /// assert_eq!(iterator.next(), Some(&Comment::from("# Comment 2")));
99    /// assert_eq!(iterator.next(), Some(&Comment::from("# Comment 3")));
100    /// assert_eq!(iterator.next(), None);
101    /// ```
102    fn into_iter(self) -> Self::IntoIter {
103        self.comments.iter()
104    }
105}
106
107impl<'a> From<Vec<Comment<'a>>> for Comments<'a> {
108    /// Create Comments from a vector of Comment
109    ///
110    /// # Arguments
111    ///
112    /// * `comments` - The vector of comments to create the collection from
113    ///
114    /// # Returns
115    ///
116    /// A new Comments collection containing the provided comments
117    fn from(comments: Vec<Comment<'a>>) -> Self {
118        Self { comments }
119    }
120}
121
122impl From<Comments<'_>> for String {
123    /// Convert Comments to a String
124    ///
125    /// # Arguments
126    ///
127    /// * `comments` - The comments collection to convert
128    ///
129    /// # Returns
130    ///
131    /// A String containing all comments joined with double newlines
132    fn from(comments: Comments<'_>) -> Self {
133        comments
134            .comments
135            .into_iter()
136            .map(Self::from)
137            .collect::<Vec<_>>()
138            .join("\n\n")
139    }
140}
141
142impl<'a> From<Vec<Fragment<'a>>> for Comments<'a> {
143    /// Create Comments from a vector of Fragment
144    ///
145    /// # Arguments
146    ///
147    /// * `ast` - The vector of fragments to filter for comments
148    ///
149    /// # Returns
150    ///
151    /// A new Comments collection containing only the Comment fragments
152    fn from(ast: Vec<Fragment<'a>>) -> Self {
153        ast.into_iter()
154            .filter_map(|values| {
155                if let Fragment::Comment(comment) = values {
156                    Some(comment)
157                } else {
158                    None
159                }
160            })
161            .collect::<Vec<Comment<'_>>>()
162            .into()
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use indoc::indoc;
169
170    use super::*;
171    use crate::body::Body;
172
173    #[test]
174    fn test_iterator_implementation() {
175        let comments = Comments::from(vec![
176            Comment::from("# Comment 1"),
177            Comment::from("# Comment 2"),
178            Comment::from("# Comment 3"),
179        ]);
180        let mut iterator = comments.iter();
181
182        assert_eq!(
183            iterator.next(),
184            Some(&Comment::from("# Comment 1")),
185            "Iterator should return the first comment"
186        );
187        assert_eq!(
188            iterator.next(),
189            Some(&Comment::from("# Comment 2")),
190            "Iterator should return the second comment"
191        );
192        assert_eq!(
193            iterator.next(),
194            Some(&Comment::from("# Comment 3")),
195            "Iterator should return the third comment"
196        );
197        assert_eq!(
198            iterator.next(),
199            None,
200            "Iterator should return None after all comments"
201        );
202    }
203
204    #[test]
205    fn test_string_conversion() {
206        let comments = Comments::from(vec![
207            Comment::from("# Message Body"),
208            Comment::from("# Another Message Body"),
209        ]);
210
211        assert_eq!(
212            String::from(comments),
213            String::from(indoc!(
214                "
215                # Message Body
216
217                # Another Message Body"
218            )),
219            "Comments should convert to a string with comments separated by double newlines"
220        );
221    }
222
223    #[test]
224    fn test_creation_from_fragments() {
225        let comments = Comments::from(vec![
226            Fragment::Comment(Comment::from("# Message Body")),
227            Fragment::Body(Body::from("Some body content")),
228            Fragment::Comment(Comment::from("# Another Message Body")),
229        ]);
230
231        assert_eq!(
232            comments,
233            Comments::from(vec![
234                Comment::from("# Message Body"),
235                Comment::from("# Another Message Body"),
236            ]),
237            "Comments should be created from fragments, filtering out non-comment fragments"
238        );
239    }
240}