bingus 0.10.0

databending made easy
Documentation
// #![deny(unused_crate_dependencies)]

#[cfg(feature = "binary")]
pub mod bin;
#[cfg(feature = "documents")]
pub mod doc;
#[cfg(feature = "fonts")]
pub mod fnt;
#[cfg(feature = "pictures")]
pub mod img;
#[cfg(feature = "music")]
pub mod snd;
#[cfg(feature = "text")]
pub mod txt;

pub type Bytes = Vec<u8>;

pub mod dynamic {
    #[cfg(feature = "text")]
    use std::string::FromUtf8Error;
    use std::{
        borrow::Cow,
        fs::File,
        io::{self, Cursor, Read, Write},
        path::Path,
    };

    #[cfg(all(feature = "documents", feature = "shiva"))]
    use super::doc::ShivaDocument;
    #[cfg(all(feature = "fonts", feature = "font-kit"))]
    use super::fnt::Font as FontKitFont;
    #[cfg(all(feature = "pictures", feature = "image"))]
    use super::img::{self, DynamicImage};
    #[cfg(feature = "music")]
    use super::snd::{self, Audio};
    #[cfg(feature = "text")]
    use super::txt::Text;
    use super::{Bendable, Bytes, IntoDataBytes, TryFromDataBytes};
    #[cfg(not(feature = "text"))]
    use std::marker::PhantomData;

    use cfg_if::cfg_if;
    #[cfg(all(feature = "fonts", feature = "font-kit"))]
    use font_kit::error::FontLoadingError;
    pub use infer::*;
    #[cfg(all(feature = "documents", feature = "printpdf"))]
    use printpdf::PdfDocument;
    #[cfg(all(feature = "documents", feature = "shiva"))]
    use shiva::core::{bytes, Document, DocumentType};
    use thiserror::Error;

    pub enum DynamicBendable<'a> {
        #[cfg(all(feature = "pictures", feature = "image"))]
        Image(DynamicImage),
        #[cfg(feature = "binary")]
        Binary(Bytes),
        #[cfg(feature = "music")]
        Sound(Audio),
        #[cfg(feature = "text")]
        Text(Text<'a>),
        #[cfg(not(feature = "text"))]
        Phantom(PhantomData<&'a ()>),
        #[cfg(all(feature = "documents", feature = "shiva"))]
        Doc(ShivaDocument),
        #[cfg(all(feature = "documents", feature = "printpdf"))]
        Archive(PdfDocument),
        Meta,
        #[cfg(all(feature = "fonts", feature = "font-kit"))]
        Font(FontKitFont),
    }

    #[cfg(feature = "shiva")]
    #[derive(Debug, Error)]
    #[error("extension is unknown by Shiva")]
    pub struct ShivaUnknownExtensionError;

    #[cfg(feature = "shiva")]
    #[derive(Debug, Error)]
    pub enum ShivaError {
        #[error("{0}")]
        UnknownExtension(#[from] ShivaUnknownExtensionError),
        #[error("{0}")]
        Anyhow(#[from] anyhow::Error),
    }

    #[derive(Debug, Error)]
    pub enum OpenError {
        #[error("io: {0}")]
        Io(#[from] io::Error),
        #[cfg(all(feature = "pictures", feature = "image"))]
        #[error("image: {0}")]
        Image(#[from] img::ImageError),
        #[cfg(feature = "music")]
        #[error("audio: {0}")]
        Audio(#[from] snd::AudioOpenError),
        #[cfg(all(feature = "documents", feature = "printpdf"))]
        #[error("pdf: {0}")]
        Pdf(String),
        #[cfg(feature = "text")]
        #[error("text: {0}")]
        Text(#[from] FromUtf8Error),
        #[cfg(all(feature = "documents", feature = "shiva"))]
        #[error("document: {0}")]
        Document(#[from] ShivaError),
        #[cfg(all(feature = "fonts", feature = "font-kit"))]
        #[error("font: {0:?}")]
        Font(#[from] FontLoadingError),
    }

    impl TryFromDataBytes for File {
        type Error = io::Error;
        type Format = Box<dyn AsRef<Path>>;

        fn try_from_data_bytes(
            bytes: Bytes,
            format: Self::Format,
            _crop: crate::Crop,
        ) -> Result<Self, Self::Error>
        where
            Self: Sized,
        {
            let mut file = File::create(format.as_ref())?;
            file.write_all(&bytes)?;
            Ok(file)
        }
    }

    impl IntoDataBytes for File {
        fn into_data_bytes(self) -> Bytes {
            self.bytes().flatten().collect() // !! will return an empty vec if it can't read the file!
        }
    }

    impl Bendable for File {
        type Unit = u8;

        /// /!\ may panic with io errors /!\
        fn map<F: Fn(Cow<Self::Unit>) -> Self::Unit + Sync>(mut self, f: F) -> Self {
            let mut bytes = Vec::new();
            self.read_to_end(&mut bytes).expect("couldn't read file");
            cfg_if! {
                if #[cfg(feature = "binary")] {
                    self.write_all(&bytes.map(f)).expect("couldn't write file");
                } else {
                    self.write_all(&bytes.into_iter().map(|e| f(Cow::Owned(e))).collect::<Vec<u8>>()).expect("couldn't write file");
                }
            }
            self
        }
    }

    pub type DynamicResult = Result<Option<DynamicBendable<'static>>, OpenError>;

    pub fn guess(t: Option<infer::Type>, bytes: Bytes) -> DynamicResult {
        use MatcherType::*;
        t.map(|t| (t.matcher_type(), t.extension()))
        .map(
            |(matcher, extension)| -> Result<DynamicBendable, OpenError> {
                Ok(match matcher {
                    #[cfg(all(feature = "pictures", feature = "image"))]
                    Image => DynamicBendable::Image(img::load_from_memory(&bytes)?),
                    #[cfg(feature = "music")]
                    Audio => DynamicBendable::Sound(crate::snd::Audio::open(Cursor::new(bytes), None)?),
                    #[cfg(all(feature = "documents", feature = "printpdf"))]
                    Archive if extension == "pdf" => DynamicBendable::Archive(
                        PdfDocument::try_from_data_bytes(
                            bytes,
                            (),
                            Default::default(),
                        )
                        .map_err(OpenError::Pdf)?,
                    ),
                    #[cfg(all(feature = "documents", feature = "shiva"))]
                    Archive | Doc => {
                        let document_type = DocumentType::from_extension(extension)
                            .ok_or(ShivaUnknownExtensionError)
                            .map_err(ShivaError::UnknownExtension)?;
                        DynamicBendable::Doc(ShivaDocument::new(
                            Document::parse(
                                &bytes::Bytes::from(bytes),
                                document_type,
                            )
                            .map_err(ShivaError::Anyhow)?,
                            document_type,
                        ))
                    }
                    #[cfg(all(feature = "fonts", feature = "font-kit"))]
                    Font => DynamicBendable::Font(FontKitFont::try_from_data_bytes(
                        bytes,
                        (),
                        Default::default(),
                    )?),
                    #[cfg(feature = "text")]
                    Text => DynamicBendable::Text(crate::txt::Text::try_from_data_bytes(
                        bytes,
                        (),
                        Default::default(),
                    ).unwrap()),
                    #[cfg(feature = "binary")]
                    _ => DynamicBendable::Binary(bytes),
                    #[cfg(not(feature = "binary"))]
                    _ => unimplemented!("no format reader available to open this thing (turn on the 'binary' feature to default to binary data)"),
                })
            },
        )
        .transpose()
    }

    pub fn open_file(path: impl AsRef<Path>) -> DynamicResult {
        open(&mut File::open(path)?)
    }

    pub fn open(source: &mut impl Read) -> DynamicResult {
        let contents = {
            let mut c = Vec::new();
            source.read_to_end(&mut c)?;
            c
        };
        guess(infer::get(&contents), contents)
    }
}

use std::{borrow::Cow, convert::Infallible};

pub trait Bendable: TryFromDataBytes + IntoDataBytes {
    type Unit;
    fn bend_into<T: TryFromDataBytes>(
        self,
        format: <T as TryFromDataBytes>::Format,
        crop: Crop,
    ) -> Result<T, <T as TryFromDataBytes>::Error> {
        T::try_from_data_bytes(self.into_data_bytes(), format, crop)
    }
    fn bend_from<T: IntoDataBytes>(
        b: T,
        format: <Self as TryFromDataBytes>::Format,
        crop: Crop,
    ) -> Result<Self, <Self as TryFromDataBytes>::Error> {
        Self::try_from_data_bytes(b.into_data_bytes(), format, crop)
    }
    fn map<F: Fn(Cow<Self::Unit>) -> Self::Unit + Sync>(self, f: F) -> Self;
}

pub trait IntoDataBytes: Sized {
    fn into_data_bytes(self) -> Bytes;
}

#[derive(Default)]
pub enum Crop {
    Start,
    #[default]
    End,
}

pub trait TryFromDataBytes {
    type Error;
    type Format;
    fn try_from_data_bytes(
        bytes: Bytes,
        format: Self::Format,
        crop: Crop,
    ) -> Result<Self, Self::Error>
    where
        Self: Sized;
}

pub trait FromDataBytes {
    type Format;
    fn from_data_bytes(bytes: Bytes, format: Self::Format, crop: Crop) -> Self
    where
        Self: Sized;
}

impl<F, T> FromDataBytes for T
where
    T: TryFromDataBytes<Error = Infallible, Format = F>,
{
    type Format = <T as TryFromDataBytes>::Format;
    fn from_data_bytes(bytes: Bytes, format: Self::Format, crop: Crop) -> Self {
        T::try_from_data_bytes(bytes, format, crop).unwrap_or_else(|_| unreachable!())
    }
}