bingus/
lib.rs

1#[cfg(feature = "binary")]
2pub mod bin;
3#[cfg(feature = "documents")]
4pub mod doc;
5#[cfg(feature = "fonts")]
6pub mod fnt;
7#[cfg(feature = "pictures")]
8pub mod img;
9#[cfg(feature = "music")]
10pub mod snd;
11#[cfg(feature = "text")]
12pub mod txt;
13
14pub type Bytes = Vec<u8>;
15
16pub mod dynamic {
17    #[cfg(feature = "text")]
18    use std::string::FromUtf8Error;
19    use std::{
20        borrow::Cow,
21        fs::File,
22        io::{self, Cursor, Read, Write},
23        path::Path,
24    };
25
26    #[cfg(feature = "documents")]
27    use super::doc::ShivaDocument;
28    #[cfg(feature = "fonts")]
29    use super::fnt::Font as FontKitFont;
30    #[cfg(feature = "pictures")]
31    use super::img::{self, DynamicImage};
32    #[cfg(feature = "music")]
33    use super::snd::{self, Audio};
34    #[cfg(feature = "text")]
35    use super::txt::Text;
36    use super::{Bendable, Bytes, IntoDataBytes, TryFromDataBytes};
37    #[cfg(not(feature = "text"))]
38    use std::marker::PhantomData;
39
40    use cfg_if::cfg_if;
41    #[cfg(feature = "fonts")]
42    use font_kit::error::FontLoadingError;
43    pub use infer::*;
44    #[cfg(feature = "documents")]
45    use printpdf::PdfDocument;
46    #[cfg(feature = "documents")]
47    use shiva::core::{bytes, Document, DocumentType};
48    use thiserror::Error;
49
50    pub enum DynamicBendable<'a> {
51        #[cfg(feature = "pictures")]
52        Image(DynamicImage),
53        #[cfg(feature = "binary")]
54        Binary(Bytes),
55        #[cfg(feature = "music")]
56        Sound(Audio),
57        #[cfg(feature = "text")]
58        Text(Text<'a>),
59        #[cfg(not(feature = "text"))]
60        Phantom(PhantomData<&'a ()>),
61        #[cfg(feature = "documents")]
62        Doc(ShivaDocument),
63        #[cfg(feature = "documents")]
64        Archive(PdfDocument),
65        Meta,
66        #[cfg(feature = "fonts")]
67        Font(FontKitFont),
68    }
69
70    #[cfg(feature = "shiva")]
71    #[derive(Debug, Error)]
72    #[error("extension is unknown by Shiva")]
73    pub struct ShivaUnknownExtensionError;
74
75    #[cfg(feature = "shiva")]
76    #[derive(Debug, Error)]
77    pub enum ShivaError {
78        #[error("{0}")]
79        UnknownExtension(#[from] ShivaUnknownExtensionError),
80        #[error("{0}")]
81        Anyhow(#[from] anyhow::Error),
82    }
83
84    #[derive(Debug, Error)]
85    pub enum OpenError {
86        #[error("io: {0}")]
87        Io(#[from] io::Error),
88        #[cfg(feature = "pictures")]
89        #[error("image: {0}")]
90        Image(#[from] img::ImageError),
91        #[cfg(feature = "music")]
92        #[error("audio: {0}")]
93        Audio(#[from] snd::AudioOpenError),
94        #[cfg(feature = "documents")]
95        #[error("pdf: {0}")]
96        Pdf(String),
97        #[cfg(feature = "text")]
98        #[error("text: {0}")]
99        Text(#[from] FromUtf8Error),
100        #[cfg(feature = "documents")]
101        #[error("document: {0}")]
102        Document(#[from] ShivaError),
103        #[cfg(feature = "fonts")]
104        #[error("font: {0:?}")]
105        Font(#[from] FontLoadingError),
106    }
107
108    impl TryFromDataBytes for File {
109        type Error = io::Error;
110        type Format = Box<dyn AsRef<Path>>;
111
112        fn try_from_data_bytes(
113            bytes: Bytes,
114            format: Self::Format,
115            _crop: crate::Crop,
116        ) -> Result<Self, Self::Error>
117        where
118            Self: Sized,
119        {
120            let mut file = File::create(format.as_ref())?;
121            file.write_all(&bytes)?;
122            Ok(file)
123        }
124    }
125
126    impl IntoDataBytes for File {
127        fn into_data_bytes(self) -> Bytes {
128            self.bytes().flatten().collect() // !! will return an empty vec if it can't read the file!
129        }
130    }
131
132    impl Bendable for File {
133        type Unit = u8;
134
135        /// /!\ may panic with io errors /!\
136        fn map<F: Fn(Cow<Self::Unit>) -> Self::Unit + Sync>(mut self, f: F) -> Self {
137            let mut bytes = Vec::new();
138            self.read_to_end(&mut bytes).expect("couldn't read file");
139            cfg_if! {
140                if #[cfg(feature = "binary")] {
141                    self.write_all(&bytes.map(f)).expect("couldn't write file");
142                } else {
143                    self.write_all(&bytes.into_iter().map(|e| f(Cow::Owned(e))).collect::<Vec<u8>>()).expect("couldn't write file");
144                }
145            }
146            self
147        }
148    }
149
150    pub type DynamicResult = Result<Option<DynamicBendable<'static>>, OpenError>;
151
152    pub fn guess(t: Option<infer::Type>, bytes: Bytes) -> DynamicResult {
153        use MatcherType::*;
154        t.map(|t| (t.matcher_type(), t.extension()))
155        .map(
156            |(matcher, extension)| -> Result<DynamicBendable, OpenError> {
157                Ok(match matcher {
158                    #[cfg(feature = "pictures")]
159                    Image => DynamicBendable::Image(img::load_from_memory(&bytes)?),
160                    #[cfg(feature = "music")]
161                    Audio => DynamicBendable::Sound(crate::snd::Audio::open(Cursor::new(bytes), None)?),
162                    #[cfg(feature = "documents")]
163                    Archive if extension == "pdf" => DynamicBendable::Archive(
164                        PdfDocument::try_from_data_bytes(
165                            bytes,
166                            (),
167                            Default::default(),
168                        )
169                        .map_err(OpenError::Pdf)?,
170                    ),
171                    #[cfg(feature = "documents")]
172                    Archive | Doc => {
173                        let document_type = DocumentType::from_extension(extension)
174                            .ok_or(ShivaUnknownExtensionError)
175                            .map_err(ShivaError::UnknownExtension)?;
176                        DynamicBendable::Doc(ShivaDocument::new(
177                            Document::parse(
178                                &bytes::Bytes::from(bytes),
179                                document_type,
180                            )
181                            .map_err(ShivaError::Anyhow)?,
182                            document_type,
183                        ))
184                    }
185                    #[cfg(feature = "fonts")]
186                    Font => DynamicBendable::Font(FontKitFont::try_from_data_bytes(
187                        bytes,
188                        (),
189                        Default::default(),
190                    )?),
191                    #[cfg(feature = "text")]
192                    Text => DynamicBendable::Text(crate::txt::Text::try_from_data_bytes(
193                        bytes,
194                        (),
195                        Default::default(),
196                    )?),
197                    #[cfg(feature = "binary")]
198                    _ => DynamicBendable::Binary(bytes),
199                    #[cfg(not(feature = "binary"))]
200                    _ => unimplemented!("no format reader available to open this thing (turn on the 'binary' feature to default to binary data)"),
201                })
202            },
203        )
204        .transpose()
205    }
206
207    pub fn open_file(path: impl AsRef<Path>) -> DynamicResult {
208        open(&mut File::open(path)?)
209    }
210
211    pub fn open(source: &mut impl Read) -> DynamicResult {
212        let contents = {
213            let mut c = Vec::new();
214            source.read_to_end(&mut c)?;
215            c
216        };
217        guess(infer::get(&contents), contents)
218    }
219}
220
221use std::{borrow::Cow, convert::Infallible};
222
223pub trait Bendable: TryFromDataBytes + IntoDataBytes {
224    type Unit;
225    fn bend_into<T: TryFromDataBytes>(
226        self,
227        format: <T as TryFromDataBytes>::Format,
228        crop: Crop,
229    ) -> Result<T, <T as TryFromDataBytes>::Error> {
230        T::try_from_data_bytes(self.into_data_bytes(), format, crop)
231    }
232    fn bend_from<T: IntoDataBytes>(
233        b: T,
234        format: <Self as TryFromDataBytes>::Format,
235        crop: Crop,
236    ) -> Result<Self, <Self as TryFromDataBytes>::Error> {
237        Self::try_from_data_bytes(b.into_data_bytes(), format, crop)
238    }
239    fn map<F: Fn(Cow<Self::Unit>) -> Self::Unit + Sync>(self, f: F) -> Self;
240}
241
242pub trait IntoDataBytes: Sized {
243    fn into_data_bytes(self) -> Bytes;
244}
245
246#[derive(Default)]
247pub enum Crop {
248    Start,
249    #[default]
250    End,
251}
252
253pub trait TryFromDataBytes {
254    type Error;
255    type Format;
256    fn try_from_data_bytes(
257        bytes: Bytes,
258        format: Self::Format,
259        crop: Crop,
260    ) -> Result<Self, Self::Error>
261    where
262        Self: Sized;
263}
264
265pub trait FromDataBytes {
266    type Format;
267    fn from_data_bytes(bytes: Bytes, format: Self::Format, crop: Crop) -> Self
268    where
269        Self: Sized;
270}
271
272impl<F, T> FromDataBytes for T
273where
274    T: TryFromDataBytes<Error = Infallible, Format = F>,
275{
276    type Format = <T as TryFromDataBytes>::Format;
277    fn from_data_bytes(bytes: Bytes, format: Self::Format, crop: Crop) -> Self {
278        T::try_from_data_bytes(bytes, format, crop).unwrap_or_else(|_| unreachable!())
279    }
280}