1use std::{
2 fmt,
3 num::ParseIntError,
4 str::{self, FromStr},
5};
6
7use thiserror::Error;
8
9#[derive(Clone, Debug, PartialEq, Eq, Hash)]
12pub struct Author {
13 pub name: String,
17 pub email: String,
21 pub time: Time,
23}
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
27pub struct Time {
28 seconds: i64,
29 offset: i32,
30}
31
32impl Time {
33 pub fn new(seconds: i64, offset: i32) -> Self {
34 Self { seconds, offset }
35 }
36
37 pub fn seconds(&self) -> i64 {
39 self.seconds
40 }
41
42 pub fn offset(&self) -> i32 {
44 self.offset
45 }
46}
47
48impl From<Time> for git2::Time {
49 fn from(t: Time) -> Self {
50 Self::new(t.seconds, t.offset)
51 }
52}
53
54impl From<git2::Time> for Time {
55 fn from(t: git2::Time) -> Self {
56 Self::new(t.seconds(), t.offset_minutes())
57 }
58}
59
60impl fmt::Display for Time {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 let sign = if self.offset.is_negative() { '-' } else { '+' };
63 write!(f, "{} {}{:0>4}", self.seconds, sign, self.offset.abs())
64 }
65}
66
67impl fmt::Display for Author {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 write!(f, "{} <{}> {}", self.name, self.email, self.time,)
70 }
71}
72
73impl TryFrom<&Author> for git2::Signature<'_> {
74 type Error = git2::Error;
75
76 fn try_from(person: &Author) -> Result<Self, Self::Error> {
77 let time = git2::Time::new(person.time.seconds, person.time.offset);
78 git2::Signature::new(&person.name, &person.email, &time)
79 }
80}
81
82impl<'a> TryFrom<&git2::Signature<'a>> for Author {
83 type Error = str::Utf8Error;
84
85 fn try_from(value: &git2::Signature<'a>) -> Result<Self, Self::Error> {
86 Ok(Self {
87 name: str::from_utf8(value.name_bytes())?.to_string(),
88 email: str::from_utf8(value.email_bytes())?.to_string(),
89 time: value.when().into(),
90 })
91 }
92}
93
94#[derive(Debug, Error)]
95pub enum ParseError {
96 #[error("missing '{0}' while parsing person signature")]
97 Missing(&'static str),
98 #[error("offset was incorrect format while parsing person signature")]
99 Offset(#[source] ParseIntError),
100 #[error("time was incorrect format while parsing person signature")]
101 Time(#[source] ParseIntError),
102 #[error("time offset is expected to be '+'/'-' for a person siganture")]
103 UnknownOffset,
104}
105
106impl FromStr for Author {
107 type Err = ParseError;
108
109 fn from_str(s: &str) -> Result<Self, Self::Err> {
110 let mut components = s.split(' ');
111 let offset = match components.next_back() {
112 None => return Err(ParseError::Missing("offset")),
113 Some(offset) => offset.parse::<i32>().map_err(ParseError::Offset)?,
114 };
115 let time = match components.next_back() {
116 None => return Err(ParseError::Missing("time")),
117 Some(time) => time.parse::<i64>().map_err(ParseError::Time)?,
118 };
119 let time = Time::new(time, offset);
120
121 let email = components
122 .next_back()
123 .ok_or(ParseError::Missing("email"))?
124 .trim_matches(|c| c == '<' || c == '>')
125 .to_owned();
126 let name = components.collect::<Vec<_>>().join(" ");
127 Ok(Self { name, email, time })
128 }
129}