blob: 398f11659ffbd5b71f7f7aa62e4e37bf1828e750 [file] [log] [blame]
use core::ops::Range;
use std::ffi::CStr;
use std::ffi::CString;
use std::ptr;
use libc::{c_char, c_int};
use crate::util::Binding;
use crate::{raw, Buf, Error, IntoCString};
/// Clean up a message, removing extraneous whitespace, and ensure that the
/// message ends with a newline. If `comment_char` is `Some`, also remove comment
/// lines starting with that character.
pub fn message_prettify<T: IntoCString>(
message: T,
comment_char: Option<u8>,
) -> Result<String, Error> {
_message_prettify(message.into_c_string()?, comment_char)
}
fn _message_prettify(message: CString, comment_char: Option<u8>) -> Result<String, Error> {
let ret = Buf::new();
unsafe {
try_call!(raw::git_message_prettify(
ret.raw(),
message,
comment_char.is_some() as c_int,
comment_char.unwrap_or(0) as c_char
));
}
Ok(ret.as_str().unwrap().to_string())
}
/// The default comment character for `message_prettify` ('#')
pub const DEFAULT_COMMENT_CHAR: Option<u8> = Some(b'#');
/// Get the trailers for the given message.
///
/// Use this function when you are dealing with a UTF-8-encoded message.
pub fn message_trailers_strs(message: &str) -> Result<MessageTrailersStrs, Error> {
_message_trailers(message.into_c_string()?).map(|res| MessageTrailersStrs(res))
}
/// Get the trailers for the given message.
///
/// Use this function when the message might not be UTF-8-encoded,
/// or if you want to handle the returned trailer key–value pairs
/// as bytes.
pub fn message_trailers_bytes<S: IntoCString>(message: S) -> Result<MessageTrailersBytes, Error> {
_message_trailers(message.into_c_string()?).map(|res| MessageTrailersBytes(res))
}
fn _message_trailers(message: CString) -> Result<MessageTrailers, Error> {
let ret = MessageTrailers::new();
unsafe {
try_call!(raw::git_message_trailers(ret.raw(), message));
}
Ok(ret)
}
/// Collection of UTF-8-encoded trailers.
///
/// Use `iter()` to get access to the values.
pub struct MessageTrailersStrs(MessageTrailers);
impl MessageTrailersStrs {
/// Create a borrowed iterator.
pub fn iter(&self) -> MessageTrailersStrsIterator<'_> {
MessageTrailersStrsIterator(self.0.iter())
}
/// The number of trailer key–value pairs.
pub fn len(&self) -> usize {
self.0.len()
}
/// Convert to the “bytes” variant.
pub fn to_bytes(self) -> MessageTrailersBytes {
MessageTrailersBytes(self.0)
}
}
/// Collection of unencoded (bytes) trailers.
///
/// Use `iter()` to get access to the values.
pub struct MessageTrailersBytes(MessageTrailers);
impl MessageTrailersBytes {
/// Create a borrowed iterator.
pub fn iter(&self) -> MessageTrailersBytesIterator<'_> {
MessageTrailersBytesIterator(self.0.iter())
}
/// The number of trailer key–value pairs.
pub fn len(&self) -> usize {
self.0.len()
}
}
struct MessageTrailers {
raw: raw::git_message_trailer_array,
}
impl MessageTrailers {
fn new() -> MessageTrailers {
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 _)
}
}
fn iter(&self) -> MessageTrailersIterator<'_> {
MessageTrailersIterator {
trailers: self,
range: Range {
start: 0,
end: self.raw.count,
},
}
}
fn len(&self) -> usize {
self.raw.count
}
}
impl Drop for MessageTrailers {
fn drop(&mut self) {
unsafe {
raw::git_message_trailer_array_free(&mut self.raw);
}
}
}
impl Binding for MessageTrailers {
type Raw = *mut raw::git_message_trailer_array;
unsafe fn from_raw(raw: *mut raw::git_message_trailer_array) -> MessageTrailers {
MessageTrailers { raw: *raw }
}
fn raw(&self) -> *mut raw::git_message_trailer_array {
&self.raw as *const _ as *mut _
}
}
struct MessageTrailersIterator<'a> {
trailers: &'a MessageTrailers,
range: Range<usize>,
}
fn to_raw_tuple(trailers: &MessageTrailers, index: usize) -> (*const c_char, *const c_char) {
unsafe {
let addr = trailers.raw.trailers.wrapping_add(index);
((*addr).key, (*addr).value)
}
}
/// Borrowed iterator over the UTF-8-encoded trailers.
pub struct MessageTrailersStrsIterator<'a>(MessageTrailersIterator<'a>);
impl<'pair> Iterator for MessageTrailersStrsIterator<'pair> {
type Item = (&'pair str, &'pair str);
fn next(&mut self) -> Option<Self::Item> {
self.0
.range
.next()
.map(|index| to_str_tuple(&self.0.trailers, index))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.range.size_hint()
}
}
impl ExactSizeIterator for MessageTrailersStrsIterator<'_> {
fn len(&self) -> usize {
self.0.range.len()
}
}
impl DoubleEndedIterator for MessageTrailersStrsIterator<'_> {
fn next_back(&mut self) -> Option<Self::Item> {
self.0
.range
.next_back()
.map(|index| to_str_tuple(&self.0.trailers, index))
}
}
fn to_str_tuple(trailers: &MessageTrailers, index: usize) -> (&str, &str) {
unsafe {
let (rkey, rvalue) = to_raw_tuple(&trailers, index);
let key = CStr::from_ptr(rkey).to_str().unwrap();
let value = CStr::from_ptr(rvalue).to_str().unwrap();
(key, value)
}
}
/// Borrowed iterator over the raw (bytes) trailers.
pub struct MessageTrailersBytesIterator<'a>(MessageTrailersIterator<'a>);
impl<'pair> Iterator for MessageTrailersBytesIterator<'pair> {
type Item = (&'pair [u8], &'pair [u8]);
fn next(&mut self) -> Option<Self::Item> {
self.0
.range
.next()
.map(|index| to_bytes_tuple(&self.0.trailers, index))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.range.size_hint()
}
}
impl ExactSizeIterator for MessageTrailersBytesIterator<'_> {
fn len(&self) -> usize {
self.0.range.len()
}
}
impl DoubleEndedIterator for MessageTrailersBytesIterator<'_> {
fn next_back(&mut self) -> Option<Self::Item> {
self.0
.range
.next_back()
.map(|index| to_bytes_tuple(&self.0.trailers, index))
}
}
fn to_bytes_tuple(trailers: &MessageTrailers, index: usize) -> (&[u8], &[u8]) {
unsafe {
let (rkey, rvalue) = to_raw_tuple(&trailers, index);
let key = CStr::from_ptr(rkey).to_bytes();
let value = CStr::from_ptr(rvalue).to_bytes();
(key, value)
}
}
#[cfg(test)]
mod tests {
#[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.
assert_eq!(message_prettify("1\n\n\n2", None).unwrap(), "1\n\n2\n");
assert_eq!(
message_prettify("1\n\n\n2\n\n\n3", None).unwrap(),
"1\n\n2\n\n3\n"
);
assert_eq!(
message_prettify("1\n# comment\n# more", None).unwrap(),
"1\n# comment\n# more\n"
);
assert_eq!(
message_prettify("1\n# comment\n# more", DEFAULT_COMMENT_CHAR).unwrap(),
"1\n"
);
assert_eq!(
message_prettify("1\n; comment\n; more", Some(';' as u8)).unwrap(),
"1\n"
);
}
#[test]
fn trailers() {
use crate::{message_trailers_bytes, message_trailers_strs, MessageTrailersStrs};
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_strs(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_strs(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_strs(message3).unwrap()));
// Raw bytes message; not valid UTF-8
// Source: https://stackoverflow.com/a/3886015/1725151
let message4 = b"
Be honest guys
Am I a malformed brussels sprout?
Signed-off-by: Lieutenant \xe2\x28\xa1prout
";
let trailer = message_trailers_bytes(&message4[..]).unwrap();
let expected = (&b"Signed-off-by"[..], &b"Lieutenant \xe2\x28\xa1prout"[..]);
let actual = trailer.iter().next().unwrap();
assert_eq!(expected, actual);
fn to_map(trailers: &MessageTrailersStrs) -> HashMap<&str, &str> {
let mut map = HashMap::with_capacity(trailers.len());
for (key, value) in trailers.iter() {
map.insert(key, value);
}
map
}
}
}