1
+ use core:: ops:: Range ;
2
+ use std:: ffi:: CStr ;
1
3
use std:: ffi:: CString ;
4
+ use std:: ptr;
2
5
3
6
use libc:: { c_char, c_int} ;
4
7
@@ -31,12 +34,216 @@ fn _message_prettify(message: CString, comment_char: Option<u8>) -> Result<Strin
31
34
/// The default comment character for `message_prettify` ('#')
32
35
pub const DEFAULT_COMMENT_CHAR : Option < u8 > = Some ( b'#' ) ;
33
36
37
+ /// Get the trailers for the given message.
38
+ ///
39
+ /// Use this function when you are dealing with a UTF-8-encoded message.
40
+ pub fn message_trailers_strs ( message : & str ) -> Result < MessageTrailersStrs , Error > {
41
+ _message_trailers ( message. into_c_string ( ) ?) . map ( |res| MessageTrailersStrs ( res) )
42
+ }
43
+
44
+ /// Get the trailers for the given message.
45
+ ///
46
+ /// Use this function when the message might not be UTF-8-encoded,
47
+ /// or if you want to handle the returned trailer key–value pairs
48
+ /// as bytes.
49
+ pub fn message_trailers_bytes < S : IntoCString > ( message : S ) -> Result < MessageTrailersBytes , Error > {
50
+ _message_trailers ( message. into_c_string ( ) ?) . map ( |res| MessageTrailersBytes ( res) )
51
+ }
52
+
53
+ fn _message_trailers ( message : CString ) -> Result < MessageTrailers , Error > {
54
+ let ret = MessageTrailers :: new ( ) ;
55
+ unsafe {
56
+ try_call ! ( raw:: git_message_trailers( ret. raw( ) , message) ) ;
57
+ }
58
+ Ok ( ret)
59
+ }
60
+
61
+ /// Collection of UTF-8-encoded trailers.
62
+ ///
63
+ /// Use `iter()` to get access to the values.
64
+ pub struct MessageTrailersStrs ( MessageTrailers ) ;
65
+
66
+ impl MessageTrailersStrs {
67
+ /// Create a borrowed iterator.
68
+ pub fn iter ( & self ) -> MessageTrailersStrsIterator < ' _ > {
69
+ MessageTrailersStrsIterator ( self . 0 . iter ( ) )
70
+ }
71
+ /// The number of trailer key–value pairs.
72
+ pub fn len ( & self ) -> usize {
73
+ self . 0 . len ( )
74
+ }
75
+ /// Convert to the “bytes” variant.
76
+ pub fn to_bytes ( self ) -> MessageTrailersBytes {
77
+ MessageTrailersBytes ( self . 0 )
78
+ }
79
+ }
80
+
81
+ /// Collection of unencoded (bytes) trailers.
82
+ ///
83
+ /// Use `iter()` to get access to the values.
84
+ pub struct MessageTrailersBytes ( MessageTrailers ) ;
85
+
86
+ impl MessageTrailersBytes {
87
+ /// Create a borrowed iterator.
88
+ pub fn iter ( & self ) -> MessageTrailersBytesIterator < ' _ > {
89
+ MessageTrailersBytesIterator ( self . 0 . iter ( ) )
90
+ }
91
+ /// The number of trailer key–value pairs.
92
+ pub fn len ( & self ) -> usize {
93
+ self . 0 . len ( )
94
+ }
95
+ }
96
+
97
+ struct MessageTrailers {
98
+ raw : raw:: git_message_trailer_array ,
99
+ }
100
+
101
+ impl MessageTrailers {
102
+ fn new ( ) -> MessageTrailers {
103
+ crate :: init ( ) ;
104
+ unsafe {
105
+ Binding :: from_raw ( & mut raw:: git_message_trailer_array {
106
+ trailers : ptr:: null_mut ( ) ,
107
+ count : 0 ,
108
+ _trailer_block : ptr:: null_mut ( ) ,
109
+ } as * mut _ )
110
+ }
111
+ }
112
+ fn iter ( & self ) -> MessageTrailersIterator < ' _ > {
113
+ MessageTrailersIterator {
114
+ trailers : self ,
115
+ range : Range {
116
+ start : 0 ,
117
+ end : self . raw . count ,
118
+ } ,
119
+ }
120
+ }
121
+ fn len ( & self ) -> usize {
122
+ self . raw . count
123
+ }
124
+ }
125
+
126
+ impl Drop for MessageTrailers {
127
+ fn drop ( & mut self ) {
128
+ unsafe {
129
+ raw:: git_message_trailer_array_free ( & mut self . raw ) ;
130
+ }
131
+ }
132
+ }
133
+
134
+ impl Binding for MessageTrailers {
135
+ type Raw = * mut raw:: git_message_trailer_array ;
136
+ unsafe fn from_raw ( raw : * mut raw:: git_message_trailer_array ) -> MessageTrailers {
137
+ MessageTrailers { raw : * raw }
138
+ }
139
+ fn raw ( & self ) -> * mut raw:: git_message_trailer_array {
140
+ & self . raw as * const _ as * mut _
141
+ }
142
+ }
143
+
144
+ struct MessageTrailersIterator < ' a > {
145
+ trailers : & ' a MessageTrailers ,
146
+ range : Range < usize > ,
147
+ }
148
+
149
+ fn to_raw_tuple ( trailers : & MessageTrailers , index : usize ) -> ( * const c_char , * const c_char ) {
150
+ unsafe {
151
+ let addr = trailers. raw . trailers . wrapping_add ( index) ;
152
+ ( ( * addr) . key , ( * addr) . value )
153
+ }
154
+ }
155
+
156
+ /// Borrowed iterator over the UTF-8-encoded trailers.
157
+ pub struct MessageTrailersStrsIterator < ' a > ( MessageTrailersIterator < ' a > ) ;
158
+
159
+ impl < ' pair > Iterator for MessageTrailersStrsIterator < ' pair > {
160
+ type Item = ( & ' pair str , & ' pair str ) ;
161
+
162
+ fn next ( & mut self ) -> Option < Self :: Item > {
163
+ self . 0
164
+ . range
165
+ . next ( )
166
+ . map ( |index| to_str_tuple ( & self . 0 . trailers , index) )
167
+ }
168
+
169
+ fn size_hint ( & self ) -> ( usize , Option < usize > ) {
170
+ self . 0 . range . size_hint ( )
171
+ }
172
+ }
173
+
174
+ impl ExactSizeIterator for MessageTrailersStrsIterator < ' _ > {
175
+ fn len ( & self ) -> usize {
176
+ self . 0 . range . len ( )
177
+ }
178
+ }
179
+
180
+ impl DoubleEndedIterator for MessageTrailersStrsIterator < ' _ > {
181
+ fn next_back ( & mut self ) -> Option < Self :: Item > {
182
+ self . 0
183
+ . range
184
+ . next_back ( )
185
+ . map ( |index| to_str_tuple ( & self . 0 . trailers , index) )
186
+ }
187
+ }
188
+
189
+ fn to_str_tuple ( trailers : & MessageTrailers , index : usize ) -> ( & str , & str ) {
190
+ unsafe {
191
+ let ( rkey, rvalue) = to_raw_tuple ( & trailers, index) ;
192
+ let key = CStr :: from_ptr ( rkey) . to_str ( ) . unwrap ( ) ;
193
+ let value = CStr :: from_ptr ( rvalue) . to_str ( ) . unwrap ( ) ;
194
+ ( key, value)
195
+ }
196
+ }
197
+
198
+ /// Borrowed iterator over the raw (bytes) trailers.
199
+ pub struct MessageTrailersBytesIterator < ' a > ( MessageTrailersIterator < ' a > ) ;
200
+
201
+ impl < ' pair > Iterator for MessageTrailersBytesIterator < ' pair > {
202
+ type Item = ( & ' pair [ u8 ] , & ' pair [ u8 ] ) ;
203
+
204
+ fn next ( & mut self ) -> Option < Self :: Item > {
205
+ self . 0
206
+ . range
207
+ . next ( )
208
+ . map ( |index| to_bytes_tuple ( & self . 0 . trailers , index) )
209
+ }
210
+
211
+ fn size_hint ( & self ) -> ( usize , Option < usize > ) {
212
+ self . 0 . range . size_hint ( )
213
+ }
214
+ }
215
+
216
+ impl ExactSizeIterator for MessageTrailersBytesIterator < ' _ > {
217
+ fn len ( & self ) -> usize {
218
+ self . 0 . range . len ( )
219
+ }
220
+ }
221
+
222
+ impl DoubleEndedIterator for MessageTrailersBytesIterator < ' _ > {
223
+ fn next_back ( & mut self ) -> Option < Self :: Item > {
224
+ self . 0
225
+ . range
226
+ . next_back ( )
227
+ . map ( |index| to_bytes_tuple ( & self . 0 . trailers , index) )
228
+ }
229
+ }
230
+
231
+ fn to_bytes_tuple ( trailers : & MessageTrailers , index : usize ) -> ( & [ u8 ] , & [ u8 ] ) {
232
+ unsafe {
233
+ let ( rkey, rvalue) = to_raw_tuple ( & trailers, index) ;
234
+ let key = CStr :: from_ptr ( rkey) . to_bytes ( ) ;
235
+ let value = CStr :: from_ptr ( rvalue) . to_bytes ( ) ;
236
+ ( key, value)
237
+ }
238
+ }
239
+
34
240
#[ cfg( test) ]
35
241
mod tests {
36
- use crate :: { message_prettify, DEFAULT_COMMENT_CHAR } ;
37
242
38
243
#[ test]
39
244
fn prettify ( ) {
245
+ use crate :: { message_prettify, DEFAULT_COMMENT_CHAR } ;
246
+
40
247
// This does not attempt to duplicate the extensive tests for
41
248
// git_message_prettify in libgit2, just a few representative values to
42
249
// make sure the interface works as expected.
@@ -58,4 +265,80 @@ mod tests {
58
265
"1\n "
59
266
) ;
60
267
}
268
+
269
+ #[ test]
270
+ fn trailers ( ) {
271
+ use crate :: { message_trailers_bytes, message_trailers_strs, MessageTrailersStrs } ;
272
+ use std:: collections:: HashMap ;
273
+
274
+ // no trailers
275
+ let message1 = "
276
+ WHAT ARE WE HERE FOR
277
+
278
+ What are we here for?
279
+
280
+ Just to be eaten?
281
+ " ;
282
+ let expected: HashMap < & str , & str > = HashMap :: new ( ) ;
283
+ assert_eq ! ( expected, to_map( & message_trailers_strs( message1) . unwrap( ) ) ) ;
284
+
285
+ // standard PSA
286
+ let message2 = "
287
+ Attention all
288
+
289
+ We are out of tomatoes.
290
+
291
+ Spoken-by: Major Turnips
292
+ Transcribed-by: Seargant Persimmons
293
+ Signed-off-by: Colonel Kale
294
+ " ;
295
+ let expected: HashMap < & str , & str > = vec ! [
296
+ ( "Spoken-by" , "Major Turnips" ) ,
297
+ ( "Transcribed-by" , "Seargant Persimmons" ) ,
298
+ ( "Signed-off-by" , "Colonel Kale" ) ,
299
+ ]
300
+ . into_iter ( )
301
+ . collect ( ) ;
302
+ assert_eq ! ( expected, to_map( & message_trailers_strs( message2) . unwrap( ) ) ) ;
303
+
304
+ // ignore everything after `---`
305
+ let message3 = "
306
+ The fate of Seargant Green-Peppers
307
+
308
+ Seargant Green-Peppers was killed by Caterpillar Battalion 44.
309
+
310
+ Signed-off-by: Colonel Kale
311
+ ---
312
+ I never liked that guy, anyway.
313
+
314
+ Opined-by: Corporal Garlic
315
+ " ;
316
+ let expected: HashMap < & str , & str > = vec ! [ ( "Signed-off-by" , "Colonel Kale" ) ]
317
+ . into_iter ( )
318
+ . collect ( ) ;
319
+ assert_eq ! ( expected, to_map( & message_trailers_strs( message3) . unwrap( ) ) ) ;
320
+
321
+ // Raw bytes message; not valid UTF-8
322
+ // Source: https://stackoverflow.com/a/3886015/1725151
323
+ let message4 = b"
324
+ Be honest guys
325
+
326
+ Am I a malformed brussels sprout?
327
+
328
+ Signed-off-by: Lieutenant \xe2 \x28 \xa1 prout
329
+ " ;
330
+
331
+ let trailer = message_trailers_bytes ( & message4[ ..] ) . unwrap ( ) ;
332
+ let expected = ( & b"Signed-off-by" [ ..] , & b"Lieutenant \xe2 \x28 \xa1 prout" [ ..] ) ;
333
+ let actual = trailer. iter ( ) . next ( ) . unwrap ( ) ;
334
+ assert_eq ! ( expected, actual) ;
335
+
336
+ fn to_map ( trailers : & MessageTrailersStrs ) -> HashMap < & str , & str > {
337
+ let mut map = HashMap :: with_capacity ( trailers. len ( ) ) ;
338
+ for ( key, value) in trailers. iter ( ) {
339
+ map. insert ( key, value) ;
340
+ }
341
+ map
342
+ }
343
+ }
61
344
}
0 commit comments