Skip to content

Commit bba7776

Browse files
committed
feat!: Identity and IdentityRef with basic conversions, decoding and serialization.
This is useful, for example, when writing `Co-authored-by: <actor>` messages or when parsing identities of actors by themselves. * `SignatureRef::actor()` is renamed to `identity()` as it now returns this type. * `SignatureRef::empty()` was removed as it is only another name for `default()`.
1 parent 500175f commit bba7776

File tree

7 files changed

+237
-92
lines changed

7 files changed

+237
-92
lines changed

gix-actor/src/identity.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
use crate::signature::decode;
2+
use crate::{Identity, IdentityRef};
3+
use bstr::ByteSlice;
4+
5+
impl<'a> IdentityRef<'a> {
6+
/// Deserialize an identity from the given `data`.
7+
pub fn from_bytes<E>(data: &'a [u8]) -> Result<Self, nom::Err<E>>
8+
where
9+
E: nom::error::ParseError<&'a [u8]> + nom::error::ContextError<&'a [u8]>,
10+
{
11+
decode::identity(data).map(|(_, t)| t)
12+
}
13+
14+
/// Create an owned instance from this shared one.
15+
pub fn to_owned(&self) -> Identity {
16+
Identity {
17+
name: self.name.to_owned(),
18+
email: self.email.to_owned(),
19+
}
20+
}
21+
22+
/// Trim whitespace surrounding the name and email and return a new identity.
23+
pub fn trim(&self) -> IdentityRef<'a> {
24+
IdentityRef {
25+
name: self.name.trim().as_bstr(),
26+
email: self.email.trim().as_bstr(),
27+
}
28+
}
29+
}
30+
31+
mod write {
32+
use crate::signature::write::validated_token;
33+
use crate::{Identity, IdentityRef};
34+
35+
/// Output
36+
impl Identity {
37+
/// Serialize this instance to `out` in the git serialization format for signatures (but without timestamp).
38+
pub fn write_to(&self, out: impl std::io::Write) -> std::io::Result<()> {
39+
self.to_ref().write_to(out)
40+
}
41+
}
42+
43+
impl<'a> IdentityRef<'a> {
44+
/// Serialize this instance to `out` in the git serialization format for signatures (but without timestamp).
45+
pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> {
46+
out.write_all(validated_token(self.name)?)?;
47+
out.write_all(b" ")?;
48+
out.write_all(b"<")?;
49+
out.write_all(validated_token(self.email)?)?;
50+
out.write_all(b">")
51+
}
52+
}
53+
}
54+
55+
mod impls {
56+
use crate::{Identity, IdentityRef};
57+
58+
impl Identity {
59+
/// Borrow this instance as immutable
60+
pub fn to_ref(&self) -> IdentityRef<'_> {
61+
IdentityRef {
62+
name: self.name.as_ref(),
63+
email: self.email.as_ref(),
64+
}
65+
}
66+
}
67+
68+
impl From<IdentityRef<'_>> for Identity {
69+
fn from(other: IdentityRef<'_>) -> Identity {
70+
let IdentityRef { name, email } = other;
71+
Identity {
72+
name: name.to_owned(),
73+
email: email.to_owned(),
74+
}
75+
}
76+
}
77+
78+
impl<'a> From<&'a Identity> for IdentityRef<'a> {
79+
fn from(other: &'a Identity) -> IdentityRef<'a> {
80+
other.to_ref()
81+
}
82+
}
83+
}

gix-actor/src/lib.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//! This crate provides ways of identifying an actor within the git repository both in shared/mutable and mutable variants.
1+
//! This crate provides ways of identifying an actor within the git repository both in shared and mutable variants.
22
//!
33
//! ## Feature Flags
44
#![cfg_attr(
@@ -12,9 +12,31 @@
1212
use bstr::{BStr, BString};
1313
pub use gix_date::{time::Sign, Time};
1414

15+
mod identity;
1516
///
1617
pub mod signature;
1718

19+
/// A person with name and email.
20+
#[derive(Default, PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
21+
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22+
pub struct Identity {
23+
/// The actors name.
24+
pub name: BString,
25+
/// The actor's email.
26+
pub email: BString,
27+
}
28+
29+
/// A person with name and email, as reference.
30+
#[derive(Default, PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
31+
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32+
pub struct IdentityRef<'a> {
33+
/// The actors name.
34+
#[cfg_attr(feature = "serde", serde(borrow))]
35+
pub name: &'a BStr,
36+
/// The actor's email.
37+
pub email: &'a BStr,
38+
}
39+
1840
/// A mutable signature is created by an actor at a certain time.
1941
///
2042
/// Note that this is not a cryptographical signature.
@@ -32,7 +54,7 @@ pub struct Signature {
3254
/// A immutable signature is created by an actor at a certain time.
3355
///
3456
/// Note that this is not a cryptographical signature.
35-
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy, Default)]
57+
#[derive(Default, PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
3658
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3759
pub struct SignatureRef<'a> {
3860
/// The actor's name.

gix-actor/src/signature/decode.rs

Lines changed: 89 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,93 @@
1-
use bstr::ByteSlice;
2-
use btoi::btoi;
3-
use nom::{
4-
branch::alt,
5-
bytes::complete::{tag, take, take_until, take_while_m_n},
6-
character::is_digit,
7-
error::{context, ContextError, ParseError},
8-
sequence::{terminated, tuple},
9-
IResult,
10-
};
11-
12-
use crate::{Sign, SignatureRef, Time};
13-
14-
const SPACE: &[u8] = b" ";
15-
16-
/// Parse a signature from the bytes input `i` using `nom`.
17-
pub fn decode<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(
18-
i: &'a [u8],
19-
) -> IResult<&'a [u8], SignatureRef<'a>, E> {
20-
let (i, (name, email, time, tzsign, hours, minutes)) = context(
21-
"<name> <<email>> <timestamp> <+|-><HHMM>",
22-
tuple((
23-
context("<name>", terminated(take_until(&b" <"[..]), take(2usize))),
24-
context("<email>", terminated(take_until(&b"> "[..]), take(2usize))),
25-
context("<timestamp>", |i| {
26-
terminated(take_until(SPACE), take(1usize))(i).and_then(|(i, v)| {
27-
btoi::<u32>(v)
28-
.map(|v| (i, v))
29-
.map_err(|_| nom::Err::Error(E::from_error_kind(i, nom::error::ErrorKind::MapRes)))
30-
})
31-
}),
32-
context("+|-", alt((tag(b"-"), tag(b"+")))),
33-
context("HH", |i| {
34-
take_while_m_n(2usize, 2, is_digit)(i).and_then(|(i, v)| {
35-
btoi::<i32>(v)
36-
.map(|v| (i, v))
37-
.map_err(|_| nom::Err::Error(E::from_error_kind(i, nom::error::ErrorKind::MapRes)))
38-
})
39-
}),
40-
context("MM", |i| {
41-
take_while_m_n(2usize, 2, is_digit)(i).and_then(|(i, v)| {
42-
btoi::<i32>(v)
43-
.map(|v| (i, v))
44-
.map_err(|_| nom::Err::Error(E::from_error_kind(i, nom::error::ErrorKind::MapRes)))
45-
})
46-
}),
47-
)),
48-
)(i)?;
49-
50-
debug_assert!(tzsign[0] == b'-' || tzsign[0] == b'+', "parser assure it's +|- only");
51-
let sign = if tzsign[0] == b'-' { Sign::Minus } else { Sign::Plus }; //
52-
let offset = (hours * 3600 + minutes * 60) * if sign == Sign::Minus { -1 } else { 1 };
53-
54-
Ok((
55-
i,
56-
SignatureRef {
57-
name: name.as_bstr(),
58-
email: email.as_bstr(),
59-
time: Time {
60-
seconds_since_unix_epoch: time,
61-
offset_in_seconds: offset,
62-
sign,
1+
pub(crate) mod function {
2+
use bstr::ByteSlice;
3+
use btoi::btoi;
4+
use nom::{
5+
branch::alt,
6+
bytes::complete::{tag, take, take_until, take_while_m_n},
7+
character::is_digit,
8+
error::{context, ContextError, ParseError},
9+
sequence::{terminated, tuple},
10+
IResult,
11+
};
12+
13+
use crate::{IdentityRef, Sign, SignatureRef, Time};
14+
15+
const SPACE: &[u8] = b" ";
16+
17+
/// Parse a signature from the bytes input `i` using `nom`.
18+
pub fn decode<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(
19+
i: &'a [u8],
20+
) -> IResult<&'a [u8], SignatureRef<'a>, E> {
21+
let (i, (identity, _, time, tzsign, hours, minutes)) = context(
22+
"<name> <<email>> <timestamp> <+|-><HHMM>",
23+
tuple((
24+
identity,
25+
tag(b" "),
26+
context("<timestamp>", |i| {
27+
terminated(take_until(SPACE), take(1usize))(i).and_then(|(i, v)| {
28+
btoi::<u32>(v)
29+
.map(|v| (i, v))
30+
.map_err(|_| nom::Err::Error(E::from_error_kind(i, nom::error::ErrorKind::MapRes)))
31+
})
32+
}),
33+
context("+|-", alt((tag(b"-"), tag(b"+")))),
34+
context("HH", |i| {
35+
take_while_m_n(2usize, 2, is_digit)(i).and_then(|(i, v)| {
36+
btoi::<i32>(v)
37+
.map(|v| (i, v))
38+
.map_err(|_| nom::Err::Error(E::from_error_kind(i, nom::error::ErrorKind::MapRes)))
39+
})
40+
}),
41+
context("MM", |i| {
42+
take_while_m_n(2usize, 2, is_digit)(i).and_then(|(i, v)| {
43+
btoi::<i32>(v)
44+
.map(|v| (i, v))
45+
.map_err(|_| nom::Err::Error(E::from_error_kind(i, nom::error::ErrorKind::MapRes)))
46+
})
47+
}),
48+
)),
49+
)(i)?;
50+
51+
debug_assert!(tzsign[0] == b'-' || tzsign[0] == b'+', "parser assure it's +|- only");
52+
let sign = if tzsign[0] == b'-' { Sign::Minus } else { Sign::Plus }; //
53+
let offset = (hours * 3600 + minutes * 60) * if sign == Sign::Minus { -1 } else { 1 };
54+
55+
Ok((
56+
i,
57+
SignatureRef {
58+
name: identity.name,
59+
email: identity.email,
60+
time: Time {
61+
seconds_since_unix_epoch: time,
62+
offset_in_seconds: offset,
63+
sign,
64+
},
65+
},
66+
))
67+
}
68+
69+
/// Parse an identity from the bytes input `i` (like `name <email>`) using `nom`.
70+
pub fn identity<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(
71+
i: &'a [u8],
72+
) -> IResult<&'a [u8], IdentityRef<'a>, E> {
73+
let (i, (name, email)) = context(
74+
"<name> <<email>>",
75+
tuple((
76+
context("<name>", terminated(take_until(&b" <"[..]), take(2usize))),
77+
context("<email>", terminated(take_until(&b">"[..]), take(1usize))),
78+
)),
79+
)(i)?;
80+
81+
Ok((
82+
i,
83+
IdentityRef {
84+
name: name.as_bstr(),
85+
email: email.as_bstr(),
6386
},
64-
},
65-
))
87+
))
88+
}
6689
}
90+
pub use function::identity;
6791

6892
#[cfg(test)]
6993
mod tests {
@@ -141,7 +165,7 @@ mod tests {
141165
.map_err(to_bstr_err)
142166
.expect_err("parse fails as > is missing")
143167
.to_string(),
144-
"Parse error:\nTakeUntil at: 12345 -1215\nin section '<email>', at: 12345 -1215\nin section '<name> <<email>> <timestamp> <+|-><HHMM>', at: hello < 12345 -1215\n"
168+
"Parse error:\nTakeUntil at: 12345 -1215\nin section '<email>', at: 12345 -1215\nin section '<name> <<email>>', at: hello < 12345 -1215\nin section '<name> <<email>> <timestamp> <+|-><HHMM>', at: hello < 12345 -1215\n"
145169
);
146170
}
147171

gix-actor/src/signature/mod.rs

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
mod _ref {
2-
use bstr::{BStr, ByteSlice};
2+
use bstr::ByteSlice;
33

4-
use crate::{signature::decode, Signature, SignatureRef};
4+
use crate::{signature::decode, IdentityRef, Signature, SignatureRef};
55

66
impl<'a> SignatureRef<'a> {
77
/// Deserialize a signature from the given `data`.
@@ -31,8 +31,11 @@ mod _ref {
3131
}
3232

3333
/// Return the actor's name and email, effectively excluding the time stamp of this signature.
34-
pub fn actor(&self) -> (&BStr, &BStr) {
35-
(self.name, self.email)
34+
pub fn actor(&self) -> IdentityRef<'a> {
35+
IdentityRef {
36+
name: self.name,
37+
email: self.email,
38+
}
3639
}
3740
}
3841
}
@@ -41,11 +44,6 @@ mod convert {
4144
use crate::{Signature, SignatureRef};
4245

4346
impl Signature {
44-
/// An empty signature, similar to 'null'.
45-
pub fn empty() -> Self {
46-
Signature::default()
47-
}
48-
4947
/// Borrow this instance as immutable
5048
pub fn to_ref(&self) -> SignatureRef<'_> {
5149
SignatureRef {
@@ -74,31 +72,29 @@ mod convert {
7472
}
7573
}
7674

77-
mod write {
78-
use std::io;
79-
75+
pub(crate) mod write {
8076
use bstr::{BStr, ByteSlice};
8177

8278
use crate::{Signature, SignatureRef};
8379

8480
/// The Error produced by [`Signature::write_to()`].
8581
#[derive(Debug, thiserror::Error)]
8682
#[allow(missing_docs)]
87-
enum Error {
83+
pub(crate) enum Error {
8884
#[error("Signature name or email must not contain '<', '>' or \\n")]
8985
IllegalCharacter,
9086
}
9187

92-
impl From<Error> for io::Error {
88+
impl From<Error> for std::io::Error {
9389
fn from(err: Error) -> Self {
94-
io::Error::new(io::ErrorKind::Other, err)
90+
std::io::Error::new(std::io::ErrorKind::Other, err)
9591
}
9692
}
9793

9894
/// Output
9995
impl Signature {
10096
/// Serialize this instance to `out` in the git serialization format for actors.
101-
pub fn write_to(&self, out: impl io::Write) -> io::Result<()> {
97+
pub fn write_to(&self, out: impl std::io::Write) -> std::io::Result<()> {
10298
self.to_ref().write_to(out)
10399
}
104100
/// Computes the number of bytes necessary to serialize this signature
@@ -109,7 +105,7 @@ mod write {
109105

110106
impl<'a> SignatureRef<'a> {
111107
/// Serialize this instance to `out` in the git serialization format for actors.
112-
pub fn write_to(&self, mut out: impl io::Write) -> io::Result<()> {
108+
pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> {
113109
out.write_all(validated_token(self.name)?)?;
114110
out.write_all(b" ")?;
115111
out.write_all(b"<")?;
@@ -123,7 +119,7 @@ mod write {
123119
}
124120
}
125121

126-
fn validated_token(name: &BStr) -> Result<&BStr, Error> {
122+
pub(crate) fn validated_token(name: &BStr) -> Result<&BStr, Error> {
127123
if name.find_byteset(b"<>\n").is_some() {
128124
return Err(Error::IllegalCharacter);
129125
}
@@ -132,5 +128,5 @@ mod write {
132128
}
133129

134130
///
135-
mod decode;
136-
pub use decode::decode;
131+
pub mod decode;
132+
pub use decode::function::decode;

0 commit comments

Comments
 (0)