pub(crate) mod consts;
#[cfg(feature = "write")]
pub(crate) mod write;
use crate::ebook::element::Href;
use crate::util::str::{StrExt, StringExt};
use std::borrow::Cow;
use std::fmt::{Debug, Display};
use std::path::Path;
#[cfg(feature = "write")]
pub use write::ResourceContent;
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct Resource<'a> {
key: ResourceKey<'a>,
kind: ResourceKind<'a>,
}
impl<'a> Resource<'a> {
pub(crate) fn new(kind: impl Into<ResourceKind<'a>>, key: impl Into<ResourceKey<'a>>) -> Self {
Self {
kind: kind.into(),
key: key.into(),
}
}
pub(crate) fn swap_value(mut self, value: String) -> Self {
self.key = ResourceKey::Value(Cow::Owned(value));
self
}
pub(crate) fn as_static(&self) -> Resource<'static> {
Resource {
key: self.key.as_static(),
kind: self.kind.as_static(),
}
}
pub fn key(&self) -> &ResourceKey<'a> {
&self.key
}
pub fn kind(&self) -> &ResourceKind<'a> {
&self.kind
}
}
impl Display for Resource<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}@{:?}", self.kind, self.key)
}
}
impl<'a, Kind: Into<ResourceKind<'a>>, Key: Into<ResourceKey<'a>>> From<(Kind, Key)>
for Resource<'a>
{
fn from((kind, key): (Kind, Key)) -> Self {
Self::new(kind, key)
}
}
impl<'a, K: Into<ResourceKey<'a>>> From<K> for Resource<'a> {
fn from(value: K) -> Self {
Self::new(ResourceKind::UNSPECIFIED, value)
}
}
impl<'a> From<&'a Self> for Resource<'a> {
fn from(value: &'a Self) -> Self {
Self::new(&value.kind, &value.key)
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub enum ResourceKey<'a> {
Value(Cow<'a, str>),
Position(usize),
}
impl ResourceKey<'_> {
fn as_static(&self) -> ResourceKey<'static> {
match self {
Self::Value(value) => ResourceKey::Value(Cow::Owned(value.to_string())),
Self::Position(position) => ResourceKey::Position(*position),
}
}
pub fn value(&self) -> Option<&str> {
match self {
Self::Value(value) => Some(value),
Self::Position(_) => None,
}
}
pub fn position(&self) -> Option<usize> {
match self {
Self::Position(position) => Some(*position),
Self::Value(_) => None,
}
}
}
impl<'a> From<&'a Path> for ResourceKey<'a> {
fn from(path: &'a Path) -> Self {
let mut value = path.to_string_lossy();
if cfg!(windows) && value.contains('\\') {
value = Cow::Owned(value.replace('\\', "/"));
}
Self::Value(value)
}
}
impl<'a> From<Href<'a>> for ResourceKey<'a> {
fn from(value: Href<'a>) -> Self {
Self::Value(Cow::Borrowed(value.path().as_str()))
}
}
impl<'a> From<&'a str> for ResourceKey<'a> {
fn from(value: &'a str) -> Self {
Self::Value(Cow::Borrowed(value))
}
}
impl<'a> From<&'a String> for ResourceKey<'a> {
fn from(value: &'a String) -> Self {
Self::Value(Cow::Borrowed(value))
}
}
impl<'a> From<String> for ResourceKey<'a> {
fn from(value: String) -> Self {
Self::Value(Cow::Owned(value))
}
}
impl<'a> From<Cow<'a, str>> for ResourceKey<'a> {
fn from(value: Cow<'a, str>) -> Self {
Self::Value(value)
}
}
impl From<usize> for ResourceKey<'_> {
fn from(value: usize) -> Self {
Self::Position(value)
}
}
impl<'a> From<&'a Self> for ResourceKey<'a> {
fn from(value: &'a Self) -> Self {
match value {
Self::Value(value) => Self::Value(Cow::Borrowed(value)),
Self::Position(position) => Self::Position(*position),
}
}
}
#[derive(Clone, Debug, Hash, Eq)]
pub struct ResourceKind<'a>(Cow<'a, str>);
impl ResourceKind<'_> {
const _UNSPECIFIED: &'static str = "";
const _APPLICATION: &'static str = "application";
const _AUDIO: &'static str = "audio";
const _FONT: &'static str = "font";
const _IMAGE: &'static str = "image";
const _TEXT: &'static str = "text";
const _VIDEO: &'static str = "video";
pub const UNSPECIFIED: ResourceKind<'static> = Self::borrowed(Self::_UNSPECIFIED);
pub const APPLICATION: ResourceKind<'static> = Self::borrowed(Self::_APPLICATION);
pub const AUDIO: ResourceKind<'static> = Self::borrowed(Self::_AUDIO);
pub const FONT: ResourceKind<'static> = Self::borrowed(Self::_FONT);
pub const IMAGE: ResourceKind<'static> = Self::borrowed(Self::_IMAGE);
pub const TEXT: ResourceKind<'static> = Self::borrowed(Self::_TEXT);
pub const VIDEO: ResourceKind<'static> = Self::borrowed(Self::_VIDEO);
const fn borrowed(static_str: &str) -> ResourceKind<'_> {
ResourceKind(Cow::Borrowed(static_str))
}
fn as_static(&self) -> ResourceKind<'static> {
ResourceKind(Cow::Owned(self.0.to_string()))
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn maintype(&self) -> &str {
self.0.split('/').next().unwrap_or_default()
}
pub fn subtype(&self) -> &str {
self.0.split(['/', '+', ';']).nth(1).unwrap_or_default()
}
pub fn suffix(&self) -> Option<&str> {
let base_type = self.0.split(';').next()?;
base_type.rfind('+').map(|index| &base_type[index + 1..])
}
pub fn params(&self) -> Option<&str> {
self.0.find(';').map(|index| self.0[index + 1..].trim())
}
pub fn params_iter(&self) -> impl Iterator<Item = (&str, &str)> {
self.params()
.unwrap_or_default()
.split(';')
.filter_map(|param| param.split_once('='))
.map(|(key, value)| (key.trim(), value.trim()))
}
pub fn by_param(&self, param_key: &str) -> Option<&str> {
self.params_iter()
.find_map(|(key, value)| (param_key == key).then_some(value))
}
pub fn is_unspecified(&self) -> bool {
self.subtype().is_empty() || self.maintype().is_empty()
}
pub fn is_application(&self) -> bool {
self.maintype().eq_ignore_ascii_case(Self::_APPLICATION)
}
pub fn is_audio(&self) -> bool {
self.maintype().eq_ignore_ascii_case(Self::_AUDIO)
}
pub fn is_font(&self) -> bool {
if self.maintype().eq_ignore_ascii_case(Self::_FONT) {
return true;
}
let subtype = self.subtype();
subtype.starts_with_ignore_case("font-")
|| subtype.starts_with_ignore_case("x-font")
|| subtype.eq_ignore_ascii_case("vnd.ms-fontobject")
|| subtype.eq_ignore_ascii_case("vnd.ms-opentype")
}
pub fn is_image(&self) -> bool {
self.maintype().eq_ignore_ascii_case(Self::_IMAGE)
}
pub fn is_text(&self) -> bool {
self.maintype().eq_ignore_ascii_case(Self::_TEXT)
}
pub fn is_video(&self) -> bool {
self.maintype().eq_ignore_ascii_case(Self::_VIDEO)
}
}
impl PartialEq for ResourceKind<'_> {
fn eq(&self, other: &Self) -> bool {
fn extract_type<'a>(kind: &'a ResourceKind) -> (&'a str, bool) {
let mut split = kind.0.split(';');
let full_type = split
.next()
.expect("`split` guarantees at least one entry")
.trim();
let has_params = split.next().is_some();
(full_type, has_params)
}
let (self_type, self_has_params) = extract_type(self);
let (other_type, other_has_params) = extract_type(other);
if self_has_params != other_has_params || !self_type.eq_ignore_ascii_case(other_type) {
return false;
}
if !self_has_params && !other_has_params {
return true;
}
let mut self_params = self.params_iter().collect::<Vec<_>>();
let mut other_params = other.params_iter().collect::<Vec<_>>();
if self_params.len() != other_params.len() {
return false;
}
self_params.sort_unstable_by_key(|(k, _)| k.to_ascii_lowercase());
other_params.sort_unstable_by_key(|(k, _)| k.to_ascii_lowercase());
self_params
.iter()
.zip(other_params)
.all(|(&(k1, v1), (k2, v2))| k1.eq_ignore_ascii_case(k2) && v1.eq(v2))
}
}
impl Display for ResourceKind<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl AsRef<str> for ResourceKind<'_> {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl<'a> From<&'a str> for ResourceKind<'a> {
fn from(value: &'a str) -> Self {
Self(value.trim().into())
}
}
impl From<String> for ResourceKind<'_> {
fn from(mut value: String) -> Self {
value.trim_in_place();
Self(value.into())
}
}
impl<'a> From<Cow<'a, str>> for ResourceKind<'a> {
fn from(value: Cow<'a, str>) -> Self {
match value {
Cow::Borrowed(borrowed) => Self::from(borrowed),
Cow::Owned(owned) => Self::from(owned),
}
}
}
impl<'a> From<&'a Self> for ResourceKind<'a> {
fn from(value: &'a Self) -> Self {
Self(Cow::Borrowed(&*value.0))
}
}