pub mod datetime;
use crate::util::Sealed;
use std::fmt::Display;
use std::hash::Hash;
pub trait Metadata<'ebook>: Sealed {
fn version_str(&self) -> Option<&'ebook str>;
fn version(&self) -> Option<Version>;
fn published(&self) -> Option<datetime::DateTime>;
fn modified(&self) -> Option<datetime::DateTime>;
fn identifier(&self) -> Option<impl Identifier<'ebook> + 'ebook>;
fn identifiers(&self) -> impl Iterator<Item = impl Identifier<'ebook> + 'ebook> + 'ebook;
fn language(&self) -> Option<impl Language<'ebook> + 'ebook>;
fn languages(&self) -> impl Iterator<Item = impl Language<'ebook> + 'ebook> + 'ebook;
fn title(&self) -> Option<impl Title<'ebook> + 'ebook>;
fn titles(&self) -> impl Iterator<Item = impl Title<'ebook> + 'ebook> + 'ebook;
fn description(&self) -> Option<impl MetaEntry<'ebook> + 'ebook>;
fn descriptions(&self) -> impl Iterator<Item = impl MetaEntry<'ebook> + 'ebook> + 'ebook;
fn creators(&self) -> impl Iterator<Item = impl Contributor<'ebook> + 'ebook> + 'ebook;
fn contributors(&self) -> impl Iterator<Item = impl Contributor<'ebook> + 'ebook> + 'ebook;
fn publishers(&self) -> impl Iterator<Item = impl Contributor<'ebook> + 'ebook> + 'ebook;
fn generators(&self) -> impl Iterator<Item = impl MetaEntry<'ebook> + 'ebook> + 'ebook;
fn tags(&self) -> impl Iterator<Item = impl Tag<'ebook> + 'ebook> + 'ebook;
fn iter(&self) -> impl Iterator<Item = impl MetaEntry<'ebook> + 'ebook> + 'ebook;
}
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub struct Scheme<'ebook> {
source: Option<&'ebook str>,
code: &'ebook str,
}
impl<'ebook> Scheme<'ebook> {
pub(crate) fn new(source: Option<&'ebook str>, code: &'ebook str) -> Self {
Self { source, code }
}
pub fn source(&self) -> Option<&'ebook str> {
self.source
}
pub fn code(&self) -> &'ebook str {
self.code
}
}
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub struct LanguageTag<'ebook> {
scheme: Scheme<'ebook>,
kind: LanguageKind,
}
impl<'ebook> LanguageTag<'ebook> {
pub(crate) fn new(code: &'ebook str, kind: LanguageKind) -> Self {
Self {
scheme: Scheme::new((kind != LanguageKind::Unknown).then(|| kind.as_str()), code),
kind,
}
}
pub fn scheme(&self) -> Scheme<'ebook> {
self.scheme
}
pub fn kind(&self) -> LanguageKind {
self.kind
}
}
pub struct AlternateScript<'ebook> {
script: &'ebook str,
tag: LanguageTag<'ebook>,
}
impl<'ebook> AlternateScript<'ebook> {
pub(crate) fn new(script: &'ebook str, tag: LanguageTag<'ebook>) -> Self {
Self { script, tag }
}
pub fn value(&self) -> &'ebook str {
self.script
}
pub fn language(&self) -> LanguageTag<'ebook> {
self.tag
}
}
pub trait MetaEntry<'ebook>: Sealed {
fn value(&self) -> &'ebook str;
fn order(&self) -> usize;
fn file_as(&self) -> Option<&'ebook str>;
fn alternate_scripts(&self) -> impl Iterator<Item = AlternateScript<'ebook>> + 'ebook;
}
pub trait Language<'ebook>: MetaEntry<'ebook> {
fn scheme(&self) -> Scheme<'ebook>;
fn kind(&self) -> LanguageKind;
}
pub trait Identifier<'ebook>: MetaEntry<'ebook> + Eq + Hash {
fn scheme(&self) -> Option<Scheme<'ebook>>;
}
pub trait Title<'ebook>: MetaEntry<'ebook> {
fn scheme(&self) -> Option<Scheme<'ebook>>;
fn kind(&self) -> TitleKind;
}
pub trait Tag<'ebook>: MetaEntry<'ebook> {
fn scheme(&self) -> Option<Scheme<'ebook>>;
}
pub trait Contributor<'ebook>: MetaEntry<'ebook> {
fn main_role(&self) -> Option<Scheme<'ebook>>;
fn roles(&self) -> impl Iterator<Item = Scheme<'ebook>> + 'ebook;
}
#[non_exhaustive]
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub enum LanguageKind {
Bcp47,
Unknown,
}
impl LanguageKind {
pub fn as_str(&self) -> &'static str {
match self {
Self::Bcp47 => "BCP 47",
Self::Unknown => "unknown",
}
}
}
impl Display for LanguageKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[non_exhaustive]
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub enum TitleKind {
Main,
Subtitle,
Short,
Expanded,
Collection,
Edition,
Unknown,
}
impl TitleKind {
const MAIN: &'static str = "main";
const SUBTITLE: &'static str = "subtitle";
const SHORT: &'static str = "short";
const EXPANDED: &'static str = "expanded";
const COLLECTION: &'static str = "collection";
const EDITION: &'static str = "edition";
pub(super) fn from(kind: &str) -> Self {
match kind {
Self::MAIN => Self::Main,
Self::SUBTITLE => Self::Subtitle,
Self::SHORT => Self::Short,
Self::COLLECTION => Self::Collection,
Self::EDITION => Self::Edition,
Self::EXPANDED => Self::Expanded,
_ => Self::Unknown,
}
}
#[cfg(feature = "write")]
pub(super) fn as_str(&self) -> Option<&'static str> {
match self {
Self::Main => Some(Self::MAIN),
Self::Subtitle => Some(Self::SUBTITLE),
Self::Short => Some(Self::SHORT),
Self::Collection => Some(Self::COLLECTION),
Self::Edition => Some(Self::EDITION),
Self::Expanded => Some(Self::EXPANDED),
_ => None,
}
}
pub fn is_main(&self) -> bool {
matches!(self, Self::Main)
}
pub fn is_subtitle(&self) -> bool {
matches!(self, Self::Subtitle)
}
pub fn is_short(&self) -> bool {
matches!(self, Self::Short)
}
pub fn is_collection(&self) -> bool {
matches!(self, Self::Collection)
}
pub fn is_edition(&self) -> bool {
matches!(self, Self::Edition)
}
pub fn is_expanded(&self) -> bool {
matches!(self, Self::Expanded)
}
pub fn is_unknown(&self) -> bool {
matches!(self, Self::Unknown)
}
}
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Version(
pub u16,
pub u16,
);
impl Version {
pub(crate) fn from_str(version: &str) -> Option<Self> {
let mut components = version.trim().split('.').map(str::parse);
Some(Self(
components.next()?.ok()?,
components.next().unwrap_or(Ok(0)).ok()?,
))
}
pub fn major(&self) -> u16 {
self.0
}
pub fn minor(&self) -> u16 {
self.1
}
}
impl From<u16> for Version {
fn from(version: u16) -> Self {
Self(version, 0)
}
}
impl Display for Version {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}.{}", self.0, self.1)
}
}
#[cfg(test)]
mod tests {
use crate::ebook::metadata::Version;
#[test]
fn test_version_from_str() {
let expected = [
("2.0", Some(Version(2, 0))),
("3.1", Some(Version(3, 1))),
("3", Some(Version(3, 0))),
(" 3.2 ", Some(Version(3, 2))),
("", None),
("x.y", None),
("2.3-", None),
];
for (raw, expected_version) in expected {
assert_eq!(expected_version, Version::from_str(raw));
}
}
}