New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add binding for git_message_trailers
#749
Changes from 3 commits
15a7d4e
5575c3d
33ebdcd
f6fd4b7
b0015cc
e94008a
e830f7d
55ea909
e7b0306
cd4c2db
e450945
a0bd026
34fa7b5
023fb92
16321f3
70b7ebd
ed11c76
958e378
2508506
f35b68f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,7 @@ | ||
use std::ffi::CStr; | ||
use std::ffi::CString; | ||
use std::marker; | ||
use std::ptr; | ||
|
||
use libc::{c_char, c_int}; | ||
|
||
|
@@ -28,15 +31,129 @@ fn _message_prettify(message: CString, comment_char: Option<u8>) -> Result<Strin | |
Ok(ret.as_str().unwrap().to_string()) | ||
} | ||
|
||
/// Collection of trailer key–value pairs. | ||
/// | ||
/// Use `iter()` to get access to the values. | ||
pub struct MessageTrailers<'pair> { | ||
raw: raw::git_message_trailer_array, | ||
_marker: marker::PhantomData<&'pair c_char>, | ||
} | ||
|
||
impl<'pair> MessageTrailers<'pair> { | ||
fn new() -> MessageTrailers<'pair> { | ||
crate::init(); | ||
unsafe { | ||
Binding::from_raw(&mut raw::git_message_trailer_array { | ||
trailers: ptr::null_mut(), | ||
count: 0, | ||
_trailer_block: ptr::null_mut(), | ||
} as *mut _) | ||
} | ||
} | ||
/// Create a borrowed iterator. | ||
pub fn iter(&'pair self) -> MessageTrailersIterator<'pair> { | ||
MessageTrailersIterator { | ||
trailers: self, | ||
counter: 0, | ||
} | ||
} | ||
/// The number of trailer key–value pairs. | ||
pub fn len(&self) -> usize { | ||
self.raw.count | ||
} | ||
} | ||
|
||
impl<'pair> Drop for MessageTrailers<'pair> { | ||
fn drop(&mut self) { | ||
unsafe { | ||
raw::git_message_trailer_array_free(&mut self.raw); | ||
} | ||
} | ||
} | ||
|
||
impl<'pair> Binding for MessageTrailers<'pair> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this may not actually be necessary? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See rev. b0015cc |
||
type Raw = *mut raw::git_message_trailer_array; | ||
unsafe fn from_raw(raw: *mut raw::git_message_trailer_array) -> MessageTrailers<'pair> { | ||
MessageTrailers { | ||
raw: *raw, | ||
_marker: marker::PhantomData, | ||
} | ||
} | ||
fn raw(&self) -> *mut raw::git_message_trailer_array { | ||
&self.raw as *const _ as *mut _ | ||
} | ||
} | ||
|
||
struct Trailer<'pair> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a one-off struct just for the method below so I think it's fine to remove this and just inline the method into the iterator implementation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See rev. 55ea909 |
||
key: *const c_char, | ||
value: *const c_char, | ||
_marker: marker::PhantomData<&'pair c_char>, | ||
} | ||
|
||
impl<'pair> Trailer<'pair> { | ||
fn to_str_tuple(self) -> (&'pair str, &'pair str) { | ||
unsafe { | ||
let key = CStr::from_ptr(self.key).to_str().unwrap(); | ||
let value = CStr::from_ptr(self.value).to_str().unwrap(); | ||
(key, value) | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah okay I think I understand now. I confused myself thought that See rev 16321f3 |
||
} | ||
|
||
/// A borrowed iterator. | ||
pub struct MessageTrailersIterator<'pair> { | ||
trailers: &'pair MessageTrailers<'pair>, | ||
counter: usize, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One thing I might recommend is to change this to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Never heard of, but I like it! See rev. e94008a |
||
} | ||
|
||
impl<'pair> Iterator for MessageTrailersIterator<'pair> { | ||
type Item = (&'pair str, &'pair str); | ||
|
||
fn next(&mut self) -> Option<Self::Item> { | ||
if self.counter == self.trailers.raw.count { | ||
None | ||
} else { | ||
unsafe { | ||
let addr = self.trailers.raw.trailers.wrapping_add(self.counter); | ||
self.counter += 1; | ||
Some( | ||
Trailer { | ||
key: (*addr).key, | ||
value: (*addr).value, | ||
_marker: marker::PhantomData, | ||
} | ||
.to_str_tuple(), | ||
) | ||
} | ||
} | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you add implementations for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I implemented The doc says that See rev. e7b0306 |
||
|
||
/// Get the trailers for the given message. | ||
pub fn message_trailers<'pair, S: IntoCString>( | ||
message: S, | ||
) -> Result<MessageTrailers<'pair>, Error> { | ||
_message_trailers(message.into_c_string()?) | ||
} | ||
|
||
fn _message_trailers<'pair>(message: CString) -> Result<MessageTrailers<'pair>, Error> { | ||
let ret = MessageTrailers::new(); | ||
unsafe { | ||
try_call!(raw::git_message_trailers(ret.raw(), message)); | ||
} | ||
Ok(ret) | ||
} | ||
|
||
/// The default comment character for `message_prettify` ('#') | ||
pub const DEFAULT_COMMENT_CHAR: Option<u8> = Some(b'#'); | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use crate::{message_prettify, DEFAULT_COMMENT_CHAR}; | ||
|
||
#[test] | ||
fn prettify() { | ||
use crate::{message_prettify, DEFAULT_COMMENT_CHAR}; | ||
|
||
// This does not attempt to duplicate the extensive tests for | ||
// git_message_prettify in libgit2, just a few representative values to | ||
// make sure the interface works as expected. | ||
|
@@ -58,4 +175,65 @@ mod tests { | |
"1\n" | ||
); | ||
} | ||
|
||
#[test] | ||
fn trailers() { | ||
use crate::{message_trailers, MessageTrailers}; | ||
use std::collections::HashMap; | ||
|
||
// no trailers | ||
let message1 = " | ||
WHAT ARE WE HERE FOR | ||
|
||
What are we here for? | ||
|
||
Just to be eaten? | ||
"; | ||
let expected: HashMap<&str, &str> = HashMap::new(); | ||
assert_eq!(expected, to_map(&message_trailers(message1).unwrap())); | ||
|
||
// standard PSA | ||
let message2 = " | ||
Attention all | ||
|
||
We are out of tomatoes. | ||
|
||
Spoken-by: Major Turnips | ||
Transcribed-by: Seargant Persimmons | ||
Signed-off-by: Colonel Kale | ||
"; | ||
let expected: HashMap<&str, &str> = vec![ | ||
("Spoken-by", "Major Turnips"), | ||
("Transcribed-by", "Seargant Persimmons"), | ||
("Signed-off-by", "Colonel Kale"), | ||
] | ||
.into_iter() | ||
.collect(); | ||
assert_eq!(expected, to_map(&message_trailers(message2).unwrap())); | ||
|
||
// ignore everything after `---` | ||
let message3 = " | ||
The fate of Seargant Green-Peppers | ||
|
||
Seargant Green-Peppers was killed by Caterpillar Battalion 44. | ||
|
||
Signed-off-by: Colonel Kale | ||
--- | ||
I never liked that guy, anyway. | ||
|
||
Opined-by: Corporal Garlic | ||
"; | ||
let expected: HashMap<&str, &str> = vec![("Signed-off-by", "Colonel Kale")] | ||
.into_iter() | ||
.collect(); | ||
assert_eq!(expected, to_map(&message_trailers(message3).unwrap())); | ||
|
||
fn to_map<'pair>(trailers: &'pair MessageTrailers<'pair>) -> HashMap<&str, &str> { | ||
let mut map = HashMap::with_capacity(trailers.len()); | ||
for (key, value) in trailers.iter() { | ||
map.insert(key, value); | ||
} | ||
map | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe that this is a uniquely owned value, so I don't think that a
'pair
lifetime parameter here is necessary.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See rev. f6fd4b7