1+ use core:: ops:: Range ;
2+ use std:: ffi:: CStr ;
13use std:: ffi:: CString ;
4+ use std:: ptr;
25
36use libc:: { c_char, c_int} ;
47
@@ -31,12 +34,216 @@ fn _message_prettify(message: CString, comment_char: Option<u8>) -> Result<Strin
3134/// The default comment character for `message_prettify` ('#')
3235pub const DEFAULT_COMMENT_CHAR : Option < u8 > = Some ( b'#' ) ;
3336
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+
34240#[ cfg( test) ]
35241mod tests {
36- use crate :: { message_prettify, DEFAULT_COMMENT_CHAR } ;
37242
38243 #[ test]
39244 fn prettify ( ) {
245+ use crate :: { message_prettify, DEFAULT_COMMENT_CHAR } ;
246+
40247 // This does not attempt to duplicate the extensive tests for
41248 // git_message_prettify in libgit2, just a few representative values to
42249 // make sure the interface works as expected.
@@ -58,4 +265,80 @@ mod tests {
58265 "1\n "
59266 ) ;
60267 }
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+ }
61344}
0 commit comments