use std::io; use std::str::FromStr; use crate::{Error, Time}; /// A container for just enough bytes to hold the largest possible serialization of a [`Time`]. #[derive(Default, Clone)] pub struct TimeBuf { /// One past the last byte written into `buf`. end_idx: usize, /// Fixed storage large enough for the widest raw time serialization. buf: [u8; Time::MIN.size()], } /// Adapter to make [`TimeBuf`] writable only while `Time::to_str()` serializes into it. /// /// This keeps [`TimeBuf`] as plain reusable storage: callers can inspect the finished bytes through /// [`TimeBuf::as_str()`], but cannot append arbitrary bytes or leave it in a partially-written state. struct TimeBufWriter<'a>(&'a mut TimeBuf); impl io::Write for TimeBufWriter<'_> { fn write(&mut self, buf: &[u8]) -> Result { let idx = self.0.end_idx; let end_idx = idx .checked_add(buf.len()) .ok_or_else(|| io::Error::from(io::ErrorKind::OutOfMemory))?; if end_idx > Time::MIN.size() { return Err(io::Error::from(io::ErrorKind::StorageFull)); } self.0.buf[idx..end_idx].copy_from_slice(buf); self.0.end_idx = end_idx; Ok(buf.len()) } fn flush(&mut self) -> io::Result<()> { Ok(()) } } impl TimeBuf { /// Represent this instance as standard string, serialized in a format compatible with /// signature fields in Git commits, also known as anything parseable as [raw format](function::parse_header()). pub fn as_str(&self) -> &str { // Time serializes as ASCII, which is a subset of UTF-8. let time_bytes = &self.buf[..self.end_idx]; std::str::from_utf8(time_bytes).expect("time serializes as valid UTF-8") } /// Clear the previous content. fn clear(&mut self) { self.end_idx = 0; } } impl Time { /// Serialize this instance into `buf`, exactly as it would appear in the header of a Git commit, /// and return `buf` as `&str` for easy consumption. pub fn to_str<'a>(&self, buf: &'a mut TimeBuf) -> &'a str { buf.clear(); self.write_to(&mut TimeBufWriter(buf)) .expect("write to memory of just the right size cannot fail"); buf.as_str() } } impl FromStr for Time { type Err = Error; fn from_str(s: &str) -> Result { crate::parse_header(s).ok_or_else(|| Error::new_with_input("invalid time", s)) } } pub(crate) mod function; mod git; mod raw; mod relative;