use epub::doc::EpubDoc;
use std::fs::File;
use std::io::BufReader;
use std::marker::PhantomData;
use std::path::PathBuf;
use super::RawTextProcessor;
use super::errors::EpubLoaderError;
use super::text_processors::TextProcessor;
loadable_trait!(
Loadable,
EpubLoaderError,
EpubDoc<BufReader<File>>,
load,
load_with_path
);
impl Loadable for PathBuf {
fn load(self) -> Result<EpubDoc<BufReader<File>>, EpubLoaderError> {
EpubDoc::new(self).map_err(EpubLoaderError::EpubError)
}
fn load_with_path(self) -> Result<(PathBuf, EpubDoc<BufReader<File>>), EpubLoaderError> {
let contents = EpubDoc::new(&self).map_err(EpubLoaderError::EpubError);
Ok((self, contents?))
}
}
pub struct EpubFileLoader<'a, T, P = RawTextProcessor> {
iterator: Box<dyn Iterator<Item = T> + 'a>,
_processor: PhantomData<P>,
}
type EpubLoaded = Result<(PathBuf, EpubDoc<BufReader<File>>), EpubLoaderError>;
impl<'a, P> EpubFileLoader<'a, Result<PathBuf, EpubLoaderError>, P> {
pub fn load(self) -> EpubFileLoader<'a, Result<EpubDoc<BufReader<File>>, EpubLoaderError>, P> {
EpubFileLoader {
iterator: Box::new(self.iterator.map(Loadable::load)),
_processor: PhantomData,
}
}
pub fn load_with_path(self) -> EpubFileLoader<'a, EpubLoaded, P> {
EpubFileLoader {
iterator: Box::new(self.iterator.map(Loadable::load_with_path)),
_processor: PhantomData,
}
}
}
impl<'a, P> EpubFileLoader<'a, Result<PathBuf, EpubLoaderError>, P>
where
P: TextProcessor,
{
pub fn read(self) -> EpubFileLoader<'a, Result<String, EpubLoaderError>, P> {
EpubFileLoader {
iterator: Box::new(self.iterator.map(|res| {
let doc = res.load().map(EpubChapterIterator::<P>::from)?;
Ok(doc
.into_iter()
.collect::<Result<Vec<String>, EpubLoaderError>>()?
.into_iter()
.collect::<String>())
})),
_processor: PhantomData,
}
}
pub fn read_with_path(
self,
) -> EpubFileLoader<'a, Result<(PathBuf, String), EpubLoaderError>, P> {
EpubFileLoader {
iterator: Box::new(self.iterator.map(|res| {
let (path, doc) = res.load_with_path()?;
let content = EpubChapterIterator::<P>::from(doc)
.collect::<Result<Vec<String>, EpubLoaderError>>()?
.into_iter()
.collect::<String>();
Ok((path, content))
})),
_processor: PhantomData,
}
}
}
impl<'a, P> EpubFileLoader<'a, EpubDoc<BufReader<File>>, P>
where
P: TextProcessor + 'a,
{
pub fn by_chapter(self) -> EpubFileLoader<'a, Result<String, EpubLoaderError>, P> {
EpubFileLoader {
iterator: Box::new(self.iterator.flat_map(EpubChapterIterator::<P>::from)),
_processor: PhantomData,
}
}
}
type ByChapter = (PathBuf, Vec<(usize, Result<String, EpubLoaderError>)>);
impl<'a, P: TextProcessor> EpubFileLoader<'a, (PathBuf, EpubDoc<BufReader<File>>), P> {
pub fn by_chapter(self) -> EpubFileLoader<'a, ByChapter, P> {
EpubFileLoader {
iterator: Box::new(self.iterator.map(|doc| {
let (path, doc) = doc;
(
path,
EpubChapterIterator::<P>::from(doc)
.enumerate()
.collect::<Vec<_>>(),
)
})),
_processor: PhantomData,
}
}
}
impl<'a, P> EpubFileLoader<'a, ByChapter, P>
where
P: TextProcessor,
{
pub fn ignore_errors(self) -> EpubFileLoader<'a, (PathBuf, Vec<(usize, String)>), P> {
EpubFileLoader {
iterator: Box::new(self.iterator.map(|(path, chapters)| {
let chapters = chapters
.into_iter()
.filter_map(|(idx, res)| res.ok().map(|content| (idx, content)))
.collect::<Vec<_>>();
(path, chapters)
})),
_processor: PhantomData,
}
}
}
loader_scaffold!(EpubFileLoader, EpubLoaderError, dir: all_entries, extra: P);
struct EpubChapterIterator<P> {
epub: EpubDoc<BufReader<File>>,
finished: bool,
_processor: PhantomData<P>,
}
impl<P> From<EpubDoc<BufReader<File>>> for EpubChapterIterator<P> {
fn from(epub: EpubDoc<BufReader<File>>) -> Self {
Self::new(epub)
}
}
impl<P> EpubChapterIterator<P> {
fn new(epub: EpubDoc<BufReader<File>>) -> Self {
Self {
epub,
finished: false,
_processor: PhantomData,
}
}
}
impl<P> Iterator for EpubChapterIterator<P>
where
P: TextProcessor,
{
type Item = Result<String, EpubLoaderError>;
fn next(&mut self) -> Option<Self::Item> {
if self.finished {
return None;
}
while !self.finished {
let chapter = self.epub.get_current_str();
if !self.epub.go_next() {
self.finished = true;
}
if let Some((text, _)) = chapter {
return Some(
P::process(&text)
.map_err(|err| EpubLoaderError::TextProcessorError(Box::new(err))),
);
}
}
None
}
}
#[cfg(test)]
mod tests;