use std::borrow::Cow;
use std::collections::BTreeMap;
use std::fmt::{self, Display};
use std::str::FromStr;
use serde::de::value::StrDeserializer;
use serde::ser::SerializeMap;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use url::Url;
pub use numeric::*;
pub use page::*;
pub use persons::*;
pub use strings::*;
pub use time::*;
mod numeric;
mod page;
mod persons;
mod strings;
mod time;
macro_rules! serialize_display {
($t:ty) => {
impl Serialize for $t {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
};
}
macro_rules! deserialize_from_str {
($t:ty) => {
impl<'de> Deserialize<'de> for $t {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = <&'de str>::deserialize(deserializer)?;
FromStr::from_str(s).map_err(serde::de::Error::custom)
}
}
};
}
macro_rules! custom_deserialize {
($type_name:ident where $expect:literal $($additional_visitors:item)+) => {
impl<'de> Deserialize<'de> for $type_name {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use std::fmt;
use serde::de::{Visitor};
struct OurVisitor;
impl<'de> Visitor<'de> for OurVisitor {
type Value = $type_name;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str($expect)
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Self::Value::from_str(value).map_err(|e| E::custom(e.to_string()))
}
$($additional_visitors)*
}
deserializer.deserialize_any(OurVisitor)
}
}
};
}
macro_rules! derive_or_from_str {
(
$(#[$global:meta])*
$gv:vis struct $s:ident where $expect:literal {
$(
$(#[doc = $doc:literal])*
$(#[serde $serde:tt])*
$v:vis $i:ident : $t:ty
),*
$(,)?
}
) => {
$(#[$global])*
$gv struct $s {
$(
$(#[doc = $doc])*
$v $i: $t,
)*
}
crate::types::custom_deserialize!(
$s where $expect
fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
where A: serde::de::MapAccess<'de>, {
use serde::{de, Deserialize};
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
struct Inner {
$(
$(#[serde $serde])*
$i: $t,
)*
}
Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))
.map(|inner: Inner| $s { $($i: inner.$i),* })
}
);
};
}
use custom_deserialize;
use derive_or_from_str;
use deserialize_from_str;
use serialize_display;
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[non_exhaustive]
#[serde(rename_all = "kebab-case")]
pub enum EntryType {
#[serde(alias = "Article")]
Article,
#[serde(alias = "Chapter")]
Chapter,
#[serde(alias = "Entry")]
Entry,
#[serde(alias = "Anthos")]
Anthos,
#[serde(alias = "Report")]
Report,
#[serde(alias = "Thesis")]
Thesis,
#[serde(alias = "Web")]
Web,
#[serde(alias = "Scene")]
Scene,
#[serde(alias = "Artwork")]
Artwork,
#[serde(alias = "Patent")]
Patent,
#[serde(alias = "Case")]
Case,
#[serde(alias = "Newspaper")]
Newspaper,
#[serde(alias = "Legislation")]
Legislation,
#[serde(alias = "Manuscript")]
Manuscript,
#[serde(alias = "Post")]
Post,
#[serde(alias = "Misc")]
Misc,
#[serde(alias = "Performance")]
Performance,
#[serde(alias = "Periodical")]
Periodical,
#[serde(alias = "Proceedings")]
Proceedings,
#[serde(alias = "Book")]
Book,
#[serde(alias = "Blog")]
Blog,
#[serde(alias = "Reference")]
Reference,
#[serde(alias = "Conference")]
Conference,
#[serde(alias = "Anthology")]
Anthology,
#[serde(alias = "Repository")]
Repository,
#[serde(alias = "Thread")]
Thread,
#[serde(alias = "Video")]
Video,
#[serde(alias = "Audio")]
Audio,
#[serde(alias = "Exhibition")]
Exhibition,
#[serde(alias = "Original")]
Original,
}
impl EntryType {
pub(crate) fn default_parent(&self) -> Self {
match self {
Self::Article => Self::Periodical,
Self::Chapter => Self::Book,
Self::Entry => Self::Reference,
Self::Anthos => Self::Anthology,
Self::Web => Self::Web,
Self::Scene => Self::Video,
Self::Artwork => Self::Exhibition,
Self::Legislation => Self::Anthology,
Self::Post => Self::Post,
Self::Video => Self::Video,
Self::Audio => Self::Audio,
_ => Self::Misc,
}
}
}
impl FromStr for EntryType {
type Err = serde::de::value::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = StrDeserializer::<serde::de::value::Error>::new(s);
Self::deserialize(s)
}
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum DeserializationError {
#[error("malformed date")]
Date(#[from] time::DateError),
#[error("malformed duration")]
Duration(#[from] time::DurationError),
#[error("malformed person")]
Person(#[from] persons::PersonError),
#[error("malformed numeric value")]
Numeric(#[from] NumericError),
#[error("malformed URL")]
Url(#[from] url::ParseError),
#[error("malformed format string")]
FormatString(#[from] strings::ChunkedStrParseError),
#[error("expected {0}")]
Expected(&'static str),
#[error("invalid language identifier")]
InvalidLanguageIdentifier,
#[error("expected key {0}")]
ExpectedKey(&'static str),
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Eq, Hash)]
#[serde(untagged)]
pub enum MaybeTyped<T> {
Typed(T),
String(String),
}
impl<T> MaybeTyped<T> {
pub fn as_typed(&self) -> Option<&T> {
match self {
MaybeTyped::Typed(t) => Some(t),
MaybeTyped::String(_) => None,
}
}
}
impl<T: ToOwned> MaybeTyped<T> {
pub fn to_cow(&self) -> MaybeTyped<Cow<'_, T>> {
match self {
MaybeTyped::Typed(t) => MaybeTyped::Typed(Cow::Borrowed(t)),
MaybeTyped::String(s) => MaybeTyped::String(s.clone()),
}
}
}
impl<T: Display> Display for MaybeTyped<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MaybeTyped::Typed(t) => t.fmt(f),
MaybeTyped::String(s) => s.fmt(f),
}
}
}
impl<T: ToString> MaybeTyped<T> {
pub fn to_str(&self) -> Cow<'_, str> {
match self {
MaybeTyped::Typed(t) => Cow::Owned(t.to_string()),
MaybeTyped::String(s) => Cow::Borrowed(s),
}
}
}
impl<T> MaybeTyped<T>
where
T: FromStr,
{
pub(crate) fn infallible_from_str(s: &str) -> Self {
match s.parse::<T>() {
Ok(t) => MaybeTyped::Typed(t),
Err(_) => MaybeTyped::String(s.to_owned()),
}
}
}
impl<T> FromStr for MaybeTyped<T>
where
T: FromStr,
{
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self::infallible_from_str(s))
}
}
impl<T> From<T> for MaybeTyped<T> {
fn from(t: T) -> Self {
MaybeTyped::Typed(t)
}
}
derive_or_from_str! {
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct QualifiedUrl where "URL string or dictionary with keys \"url\" and \"date\"" {
pub value: Url,
#[serde(rename = "date")]
pub visit_date: Option<Date>,
}
}
impl Serialize for QualifiedUrl {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
if let Some(date) = &self.visit_date {
let mut map = serializer.serialize_map(Some(2))?;
map.serialize_entry("value", &self.value)?;
map.serialize_entry("date", date)?;
map.end()
} else {
self.value.serialize(serializer)
}
}
}
impl QualifiedUrl {
pub fn new(value: Url, visit_date: Option<Date>) -> Self {
Self { value, visit_date }
}
}
impl FromStr for QualifiedUrl {
type Err = url::ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self { value: Url::parse(s)?, visit_date: None })
}
}
impl Display for QualifiedUrl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.value.fmt(f)
}
}
derive_or_from_str! {
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Publisher where "FormatString string or dictionary with \"name\" and \"location\"" {
name: Option<FormatString>,
location: Option<FormatString>,
}
}
impl Serialize for Publisher {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
if let Some(location) = &self.location {
let mut map = serializer.serialize_map(Some(2))?;
map.serialize_entry("name", &self.name)?;
map.serialize_entry("location", location)?;
map.end()
} else {
self.name.serialize(serializer)
}
}
}
impl Publisher {
pub fn new(name: Option<FormatString>, location: Option<FormatString>) -> Self {
Self { name, location }
}
pub fn name(&self) -> Option<&FormatString> {
self.name.as_ref()
}
pub fn location(&self) -> Option<&FormatString> {
self.location.as_ref()
}
}
impl FromStr for Publisher {
type Err = ChunkedStrParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Publisher::new(Some(FormatString::from_str(s)?), None))
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Hash)]
#[serde(transparent)]
pub struct SerialNumber(pub BTreeMap<String, String>);
impl<'de> Deserialize<'de> for SerialNumber {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum Choice {
Map(BTreeMap<String, StringOrNumber>),
Other(StringOrNumber),
}
#[derive(Deserialize)]
#[serde(untagged)]
enum StringOrNumber {
String(String),
Number(i64),
UnsignedNumber(u64),
Float(f64),
}
impl Display for StringOrNumber {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::String(s) => s.fmt(formatter),
Self::Number(n) => n.fmt(formatter),
Self::UnsignedNumber(n) => n.fmt(formatter),
Self::Float(f) => f.fmt(formatter),
}
}
}
Choice::deserialize(deserializer).map(|choice| match choice {
Choice::Other(text) => SerialNumber(BTreeMap::from_iter(vec![(
"serial".to_owned(),
text.to_string(),
)])),
Choice::Map(map) => {
SerialNumber(map.into_iter().map(|(k, v)| (k, v.to_string())).collect())
}
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_num() {
let val = Numeric::from_str("1").unwrap();
assert!(val.value == NumericValue::Number(1));
assert!(val.prefix.is_none());
assert!(val.suffix.is_none());
assert_eq!(&val.to_string(), "1");
let val = Numeric::from_str("-5").unwrap();
assert!(val.value == NumericValue::Number(-5));
assert!(val.prefix.is_none());
assert!(val.suffix.is_none());
assert_eq!(&val.to_string(), "-5");
let val = Numeric::from_str("1st").unwrap();
assert!(val.value == NumericValue::Number(1));
assert!(val.prefix.is_none());
assert!(val.suffix_str() == Some("st"));
assert_eq!(&val.to_string(), "1st");
let val = Numeric::from_str("1, 2").unwrap();
assert!(
val.value
== NumericValue::Set(vec![(1, Some(NumericDelimiter::Comma)), (2, None)])
);
assert_eq!(val.to_string(), "1, 2");
let val = Numeric::from_str("A16y").unwrap();
assert!(val.value == NumericValue::Number(16));
assert!(val.prefix_str() == Some("A"));
assert!(val.suffix_str() == Some("y"));
assert_eq!(&val.to_string(), "A16y");
let val = Numeric::from_str("1-4").unwrap();
assert!(
val.value
== NumericValue::Set(vec![
(1, Some(NumericDelimiter::Hyphen)),
(4, None)
])
);
let val_other = Numeric::from_str("1 - 4").unwrap();
assert_eq!(val, val_other);
assert_eq!(&val.to_string(), "1–4");
let val = Numeric::from_str("2 , 3").unwrap();
assert!(
val.value
== NumericValue::Set(vec![(2, Some(NumericDelimiter::Comma)), (3, None)])
);
assert_eq!(&val.to_string(), "2, 3");
let val = Numeric::from_str("2 & 3 & 4").unwrap();
assert!(
val.value
== NumericValue::Set(vec![
(2, Some(NumericDelimiter::Ampersand)),
(3, Some(NumericDelimiter::Ampersand)),
(4, None)
])
);
assert_eq!(&val.to_string(), "2 & 3 & 4");
assert!(Numeric::from_str("second").is_err());
assert!(Numeric::from_str("2nd edition").is_err());
}
#[test]
#[cfg(feature = "biblatex")]
fn test_issue_227() {
let yaml = r#"
AAAnonymous_AventureMortevielle_1987:
type: Book
page-range: 100"#;
let library = crate::io::from_yaml_str(yaml).unwrap();
let entry = library.get("AAAnonymous_AventureMortevielle_1987").unwrap();
assert_eq!(
entry
.page_range
.as_ref()
.unwrap()
.as_typed()
.unwrap()
.first()
.unwrap(),
&Numeric::new(100)
);
}
}