radicle_git_ext/commit/
trailers.rs1use std::{borrow::Cow, fmt, fmt::Write, ops::Deref, str::FromStr};
2
3use git2::{MessageTrailersStrs, MessageTrailersStrsIterator};
4
5pub struct Trailers {
40 inner: MessageTrailersStrs,
41}
42
43impl Trailers {
44 pub fn parse(message: &str) -> Result<Self, git2::Error> {
45 Ok(Self {
46 inner: git2::message_trailers_strs(message)?,
47 })
48 }
49
50 pub fn iter(&self) -> Iter<'_> {
51 Iter {
52 inner: self.inner.iter(),
53 }
54 }
55
56 pub fn to_string<'a, S>(&self, sep: S) -> String
57 where
58 S: Separator<'a>,
59 {
60 let mut buf = String::new();
61 for (i, trailer) in self.iter().enumerate() {
62 if i > 0 {
63 writeln!(buf).ok();
64 }
65
66 write!(buf, "{}", trailer.display(sep.sep_for(&trailer.token))).ok();
67 }
68 writeln!(buf).ok();
69 buf
70 }
71}
72
73impl fmt::Display for Trailers {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 f.write_str(&self.to_string(": "))
76 }
77}
78
79pub trait Separator<'a> {
80 fn sep_for(&self, token: &Token) -> &'a str;
81}
82
83impl<'a> Separator<'a> for &'a str {
84 fn sep_for(&self, _: &Token) -> &'a str {
85 self
86 }
87}
88
89impl<'a, F> Separator<'a> for F
90where
91 F: Fn(&Token) -> &'a str,
92{
93 fn sep_for(&self, token: &Token) -> &'a str {
94 self(token)
95 }
96}
97
98impl FromStr for Trailers {
99 type Err = git2::Error;
100
101 fn from_str(s: &str) -> Result<Self, Self::Err> {
102 Self::parse(s)
103 }
104}
105
106pub struct Iter<'a> {
107 inner: MessageTrailersStrsIterator<'a>,
108}
109
110impl<'a> Iterator for Iter<'a> {
111 type Item = Trailer<'a>;
112
113 fn next(&mut self) -> Option<Self::Item> {
114 let (token, value) = self.inner.next()?;
115 Some(Trailer {
116 token: Token(token),
117 value: Cow::Borrowed(value),
118 })
119 }
120}
121
122#[derive(Debug, Clone, Eq, PartialEq)]
123pub struct Token<'a>(&'a str);
124
125impl Deref for Token<'_> {
126 type Target = str;
127
128 fn deref(&self) -> &Self::Target {
129 self.0
130 }
131}
132
133impl<'a> TryFrom<&'a str> for Token<'a> {
134 type Error = &'static str;
135
136 fn try_from(s: &'a str) -> Result<Self, Self::Error> {
137 let is_token = s.chars().all(|c| c.is_alphanumeric() || c == '-');
138 if is_token {
139 Ok(Token(s))
140 } else {
141 Err("token contains invalid characters")
142 }
143 }
144}
145
146pub struct Display<'a> {
147 trailer: &'a Trailer<'a>,
148 separator: &'a str,
149}
150
151impl fmt::Display for Display<'_> {
152 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
153 write!(
154 f,
155 "{}{}{}",
156 self.trailer.token.deref(),
157 self.separator,
158 self.trailer.value,
159 )
160 }
161}
162
163#[derive(Debug, Clone, Eq, PartialEq)]
167pub struct Trailer<'a> {
168 pub token: Token<'a>,
169 pub value: Cow<'a, str>,
170}
171
172impl<'a> Trailer<'a> {
173 pub fn display(&'a self, separator: &'a str) -> Display<'a> {
174 Display {
175 trailer: self,
176 separator,
177 }
178 }
179
180 pub fn to_owned(&self) -> OwnedTrailer {
181 OwnedTrailer::from(self)
182 }
183}
184
185#[derive(Debug)]
189pub struct OwnedTrailer {
190 pub token: OwnedToken,
191 pub value: String,
192}
193
194#[derive(Debug)]
195pub struct OwnedToken(String);
196
197impl Deref for OwnedToken {
198 type Target = str;
199
200 fn deref(&self) -> &Self::Target {
201 &self.0
202 }
203}
204
205impl<'a> From<&Trailer<'a>> for OwnedTrailer {
206 fn from(t: &Trailer<'a>) -> Self {
207 OwnedTrailer {
208 token: OwnedToken(t.token.0.to_string()),
209 value: t.value.to_string(),
210 }
211 }
212}
213
214impl<'a> From<Trailer<'a>> for OwnedTrailer {
215 fn from(t: Trailer<'a>) -> Self {
216 (&t).into()
217 }
218}
219
220impl<'a> From<&'a OwnedTrailer> for Trailer<'a> {
221 fn from(t: &'a OwnedTrailer) -> Self {
222 Trailer {
223 token: Token(t.token.0.as_str()),
224 value: Cow::from(&t.value),
225 }
226 }
227}