rbook/
reader.rs

1//! Sequential + random‐access [`Ebook`](super::Ebook) [`Reader`] module.
2
3pub mod errors;
4
5use crate::ebook::manifest::ManifestEntry;
6use crate::ebook::spine::SpineEntry;
7use crate::reader::errors::ReaderResult;
8
9/// A sequential + random-access [`Ebook`](super::reader) reader.
10///
11/// # Lifetime
12/// All returned [`ReaderContent<'ebook>`](ReaderContent) are tied to the lifetime of the
13/// underlying [`Ebook`](super::Ebook).
14///
15/// # Examples
16/// - Streaming over a reader's contents:
17/// ```
18/// # use rbook::{Ebook, Epub};
19/// # use rbook::reader::{Reader, ReaderContent};
20/// # use std::error::Error;
21/// # fn main() -> Result<(), Box<dyn Error>> {
22/// let epub = Epub::open("tests/ebooks/example_epub")?;
23/// let mut reader = epub.reader();
24/// # let mut count = 0;
25///
26/// // Stream over all content
27/// while let Some(Ok(content)) = reader.read_next() {
28/// #    count += 1;
29///     // process content //
30/// }
31/// # assert_eq!(5, count);
32/// # assert_eq!(count, reader.len());
33/// # Ok(())
34/// # }
35/// ```
36/// - Random access:
37/// ```
38/// # use rbook::{Ebook, Epub};
39/// # use rbook::reader::{Reader, ReaderContent};
40/// # use std::error::Error;
41/// # fn main() -> Result<(), Box<dyn Error>> {
42/// let epub = Epub::open("tests/ebooks/example_epub")?;
43/// let mut reader = epub.reader();
44///
45/// let content_a = reader.get(2)?;
46/// let content_b = reader.get("c1")?; // by idref
47///
48/// assert_eq!(2, content_a.position());
49/// assert_eq!(2, content_b.position());
50/// # assert_eq!(content_a.content(), content_b.content());
51/// # Ok(())
52/// # }
53/// ```
54pub trait Reader<'ebook> {
55    /// Resets a reader's cursor to its initial state; **before** the first entry.
56    ///
57    /// After calling this method:
58    /// - [`Self::current_position`] = [`None`]
59    /// - [`Self::remaining`] = The total number of entries ([`Self::len`])
60    ///
61    /// By default, a newly created [`Reader`] starts in this state.
62    ///
63    /// # Examples
64    /// - Assessing the current cursor position state:
65    /// ```
66    /// # use rbook::{Ebook, Epub};
67    /// # use rbook::reader::{Reader, ReaderContent};
68    /// # use std::error::Error;
69    /// # fn main() -> Result<(), Box<dyn Error>> {
70    /// let epub = Epub::open("tests/ebooks/example_epub")?;
71    /// let mut reader = epub.reader();
72    ///
73    /// // Cursor is before the first entry
74    /// assert_eq!(None, reader.current_position());
75    /// assert_eq!(5, reader.remaining());
76    ///
77    /// // Iterate over all content
78    /// for result in &mut reader {
79    ///     // process content //
80    /// }
81    ///
82    /// assert_eq!(0, reader.remaining());
83    ///
84    /// // Resetting the cursor, so it's **before** the first element
85    /// reader.reset();
86    /// assert_eq!(None, reader.current_position());
87    /// assert_eq!(5, reader.remaining());
88    ///
89    /// // Setting the cursor **at** the first element.
90    /// reader.read(0)?;
91    /// assert_eq!(Some(0), reader.current_position());
92    /// assert_eq!(4, reader.remaining());
93    /// # Ok(())
94    /// # }
95    /// ```
96    fn reset(&mut self);
97
98    /// Returns the next [`ReaderContent`] and increments a reader's cursor by one.
99    ///
100    /// # Cases
101    /// - `Some(Ok(content))`: Entry exists and reading it succeeded.  
102    /// - `Some(Err(e))`: Entry exists yet reading it failed
103    ///   (see [`ReaderError`](errors::ReaderError)).
104    /// - `None`: No next entries; ***in this case, the cursor is not incremented.***
105    ///
106    /// # Examples
107    /// - Observing how `read_next` affects the cursor position:
108    /// ```
109    /// # use rbook::{Ebook, Epub};
110    /// # use rbook::reader::Reader;
111    /// # use std::error::Error;
112    /// # fn main() -> Result<(), Box<dyn Error>> {
113    /// let epub = Epub::open("tests/ebooks/example_epub")?;
114    /// let mut reader = epub.reader();
115    ///
116    /// // Current cursor position
117    /// assert_eq!(None, reader.current_position());
118    /// // Iterate to the end
119    /// while let Some(Ok(content)) = reader.read_next() {
120    ///     // process content //
121    /// }
122    /// // The current cursor position is now at the end
123    /// assert_eq!(Some(4), reader.current_position());
124    ///
125    /// // No more next content
126    /// assert!(reader.read_next().is_none());
127    /// // The cursor is not updated
128    /// assert_eq!(Some(4), reader.current_position());
129    ///
130    /// # Ok(())
131    /// # }
132    /// ```
133    fn read_next(&mut self) -> Option<ReaderResult<impl ReaderContent<'ebook> + 'ebook>>;
134
135    /// Returns the previous [`ReaderContent`] and decrements a reader's cursor by one.
136    ///
137    /// # Cases
138    /// - `Some(Ok(content))`: Entry exists and reading it succeeded.  
139    /// - `Some(Err(e))`: Entry exists yet reading it failed
140    ///   (see [`ReaderError`](errors::ReaderError)).
141    /// - `None`: No previous entries; ***in this case, the cursor is not decremented.***
142    ///
143    /// # Examples
144    /// - Observing how `read_prev` affects the cursor position:
145    /// ```
146    /// # use rbook::{Ebook, Epub};
147    /// # use rbook::reader::Reader;
148    /// # use std::error::Error;
149    /// # fn main() -> Result<(), Box<dyn Error>> {
150    /// let epub = Epub::open("tests/ebooks/example_epub")?;
151    /// let mut reader = epub.reader();
152    ///
153    /// // Jump to the end
154    /// reader.seek(reader.len() - 1)?;
155    /// assert_eq!(Some(4), reader.current_position());
156    ///
157    /// // Iterate to the start
158    /// while let Some(Ok(content)) = reader.read_prev() {
159    ///     // ... //
160    /// }
161    /// // Current cursor position at the start
162    /// assert_eq!(Some(0), reader.current_position());
163    ///
164    /// // No more previous content
165    /// assert!(reader.read_prev().is_none());
166    /// // The cursor is not updated
167    /// assert_eq!(Some(0), reader.current_position());
168    ///
169    /// # Ok(())
170    /// # }
171    /// ```
172    fn read_prev(&mut self) -> Option<ReaderResult<impl ReaderContent<'ebook> + 'ebook>>;
173
174    /// Returns the [`ReaderContent`] that a reader's cursor is currently positioned at.
175    ///
176    /// # Cases
177    /// - `Some(Ok(content))`: Entry exists and reading it succeeded.  
178    /// - `Some(Err(e))`: Entry exists yet reading it failed
179    ///   (see [`ReaderError`](errors::ReaderError)).
180    /// - `None`: No current entry ([`Self::current_position`] is [`None`]).
181    fn read_current(&self) -> Option<ReaderResult<impl ReaderContent<'ebook> + 'ebook>>;
182
183    /// Returns the [`ReaderContent`] at the provided [`ReaderKey`]
184    /// and moves the reader’s cursor at that position.
185    ///
186    /// Equivalent to [`Self::get`], except that this method updates the cursor.
187    ///
188    /// To re-iterate from the start, prefer [`Self::reset`]
189    /// over `read(0)`, as `reset` puts the cursor **before** the first entry.
190    fn read<'a>(
191        &mut self,
192        key: impl Into<ReaderKey<'a>>,
193    ) -> ReaderResult<impl ReaderContent<'ebook> + 'ebook>;
194
195    /// Moves a reader’s cursor **at** the provided [`ReaderKey`],
196    /// returning the position the reader's cursor points to.
197    ///
198    /// To re-iterate from the start, prefer [`Self::reset`]
199    /// over `seek(0)`, as `reset` puts the cursor **before** the first entry.
200    fn seek<'a>(&mut self, key: impl Into<ReaderKey<'a>>) -> ReaderResult<usize>;
201
202    /// Returns the [`ReaderContent`] at the provided [`ReaderKey`]
203    /// without updating the reader's cursor.
204    fn get<'a>(
205        &self,
206        key: impl Into<ReaderKey<'a>>,
207    ) -> ReaderResult<impl ReaderContent<'ebook> + 'ebook>;
208
209    /// The total number of traversable [`ReaderContent`] entries in a reader.
210    ///
211    /// This method returns the same value regardless of calls to methods that mutate
212    /// a reader's cursor such as [`Self::read`].
213    ///
214    /// # See Also
215    /// - [`Self::remaining`] to find out how many entries are left relative to a cursor.
216    fn len(&self) -> usize;
217
218    /// The position of a reader’s cursor (current entry).
219    ///
220    /// Returns [`None`] if the cursor is **before** the first entry
221    /// (such as on a newly created reader or after invoking [`Self::reset`].
222    /// Otherwise, `Some(i)` where `0 <= i < len`.
223    ///
224    /// # Examples
225    /// - Retrieving the position upon navigating:
226    /// ```
227    /// # use rbook::{Ebook, Epub};
228    /// # use rbook::reader::{Reader, ReaderContent};
229    /// # use std::error::Error;
230    /// # fn main() -> Result<(), Box<dyn Error>> {
231    /// let epub = Epub::open("tests/ebooks/example_epub")?;
232    /// let mut reader = epub.reader();
233    ///
234    /// assert_eq!(None, reader.current_position());
235    ///
236    /// // Set position to `0`
237    /// reader.seek(0)?;
238    /// assert_eq!(Some(0), reader.current_position());
239    ///
240    /// reader.read_next();
241    /// assert_eq!(Some(1), reader.current_position());
242    ///
243    /// // Set position to the end
244    /// reader.seek(reader.len() - 1)?;
245    /// assert_eq!(Some(4), reader.current_position());
246    ///
247    /// // The position remains the same since the end is reached (len - 1)
248    /// assert!(reader.read_next().is_none());
249    /// assert_eq!(Some(4), reader.current_position());
250    ///
251    /// reader.reset();
252    /// assert_eq!(None, reader.current_position());
253    /// # Ok(())
254    /// # }
255    /// ```
256    fn current_position(&self) -> Option<usize>;
257
258    /// The total number of remaining traversable [`ReaderContent`]
259    /// until a reader's cursor reaches the end.
260    ///
261    /// # Examples
262    /// - Observing the number of contents remaining:
263    /// ```
264    /// # use rbook::{Ebook, Epub};
265    /// # use rbook::reader::{Reader, ReaderContent};
266    /// # use std::error::Error;
267    /// # fn main() -> Result<(), Box<dyn Error>> {
268    /// let epub = Epub::open("tests/ebooks/example_epub")?;
269    /// let mut reader = epub.reader();
270    ///
271    /// assert_eq!(5, reader.len());
272    /// assert_eq!(5, reader.remaining());
273    /// assert_eq!(None, reader.current_position());
274    ///
275    /// // `len` remains fixed while `remaining` changes:
276    /// reader.seek(3)?;
277    /// assert_eq!(5, reader.len());
278    /// assert_eq!(1, reader.remaining());
279    /// assert_eq!(Some(3), reader.current_position());
280    /// # Ok(())
281    /// # }
282    /// ```
283    fn remaining(&self) -> usize {
284        match self.current_position() {
285            Some(position) => self.len().saturating_sub(position + 1),
286            None => self.len(),
287        }
288    }
289
290    /// Returns `true` if a reader has no [`ReaderContent`] to provide; a [`Reader::len`] of `0`.
291    ///
292    /// # Examples
293    /// - Assessing if a reader has content:
294    /// ```
295    /// # use rbook::{Ebook, Epub};
296    /// # use rbook::reader::{Reader, ReaderContent};
297    /// # use std::error::Error;
298    /// # fn main() -> Result<(), Box<dyn Error>> {
299    /// let epub = Epub::open("tests/ebooks/example_epub")?;
300    /// let mut reader = epub.reader();
301    ///
302    /// assert_eq!(5, reader.len());
303    /// // The reader has 5 entries, so it is not empty:
304    /// assert!(!reader.is_empty());
305    /// # Ok(())
306    /// # }
307    /// ```
308    fn is_empty(&self) -> bool {
309        self.len() == 0
310    }
311}
312
313/// Content provided by a [`Reader`], encompassing associated data.
314///
315/// # Examples
316/// - Retrieving the content of the same entry by different [`keys`](ReaderKey):
317/// ```
318/// # use rbook::{Ebook, Epub};
319/// # use rbook::reader::{Reader, ReaderContent};
320/// # use rbook::ebook::manifest::ManifestEntry;
321/// # use std::error::Error;
322/// # fn main() -> Result<(), Box<dyn Error>> {
323/// let epub = Epub::open("tests/ebooks/example_epub")?;
324/// let mut reader = epub.reader();
325///
326/// let entry_by_idref = reader.get("cover")?;
327/// let entry_by_position = reader.get(0)?;
328/// let kind =  entry_by_idref.manifest_entry().resource_kind();
329///
330/// assert_eq!(0, entry_by_idref.position());
331/// assert_eq!(0, entry_by_position.position());
332/// assert_eq!("application/xhtml+xml", kind.as_str());
333///
334/// // Retrieving the main content
335/// let string_ref: &str = entry_by_idref.content();
336///
337/// assert_eq!(string_ref, entry_by_position.content());
338///
339/// let string_content: String = entry_by_idref.into_string(); // or .into()
340/// let bytes_content: Vec<u8> = entry_by_position.into(); // or .into_bytes()
341///
342/// assert_eq!(bytes_content, string_content.into_bytes());
343/// # Ok(())
344/// # }
345/// ```
346pub trait ReaderContent<'ebook>: PartialEq + Into<String> + Into<Vec<u8>> {
347    /// The position of reader content within a [`Reader`] (0-index-based).
348    ///
349    /// This value may not equal [`SpineEntry::order`] depending
350    /// on how a reader is configured.
351    ///
352    /// # Examples
353    /// - Showcasing different positioning regarding EPUB:
354    /// ```
355    /// # use rbook::{Ebook, Epub};
356    /// # use rbook::ebook::spine::SpineEntry;
357    /// # use rbook::epub::reader::{EpubReaderSettings, LinearBehavior};
358    /// # use rbook::reader::{Reader, ReaderContent};
359    /// # use std::error::Error;
360    /// # fn main() -> Result<(), Box<dyn Error>> {
361    /// let epub = Epub::open("tests/ebooks/example_epub")?;
362    ///
363    /// // Reader with non-linear spine entries prepended at the start of its internal buffer.
364    /// let mut reader_a = epub.reader_with(
365    ///     EpubReaderSettings::builder().linear_behavior(LinearBehavior::PrependNonLinear)
366    /// );
367    /// let content_a = reader_a.read_next().unwrap()?;
368    ///
369    /// assert_eq!(0, content_a.position());
370    /// assert_eq!(0, content_a.spine_entry().order());
371    /// assert_eq!("cover", content_a.spine_entry().idref());
372    ///
373    /// // Reader with non-linear spine entries appended at the end of its internal buffer.
374    /// let mut reader_b = epub.reader_with(
375    ///     EpubReaderSettings::builder().linear_behavior(LinearBehavior::AppendNonLinear)
376    /// );
377    /// let content_b = reader_b.read_next().unwrap()?;
378    ///
379    /// assert_eq!(0, content_b.position());
380    /// assert_eq!(1, content_b.spine_entry().order());
381    /// assert_eq!("toc", content_b.spine_entry().idref());
382    /// # Ok(())
383    /// # }
384    /// ```
385    fn position(&self) -> usize;
386
387    /// The readable content (i.e., `XHTML`, `HTML`, etc.).
388    fn content(&self) -> &str;
389
390    /// The associated [`SpineEntry`] containing reading order details.
391    fn spine_entry(&self) -> impl SpineEntry<'ebook> + 'ebook;
392
393    /// The associated [`ManifestEntry`] containing resource details.
394    fn manifest_entry(&self) -> impl ManifestEntry<'ebook> + 'ebook;
395
396    /// Takes the contained readable content string.
397    ///
398    /// This method is equivalent to calling `into::<String>()`.
399    ///
400    /// See [`Self::content`] to retrieve a reference without taking ownership.
401    ///
402    /// # Examples:
403    /// - Extracting the contained content in the form of a [`String`]:
404    /// ```
405    /// # use rbook::{Ebook, Epub};
406    /// # use rbook::reader::{Reader, ReaderContent};
407    /// # use std::error::Error;
408    /// # fn main() -> Result<(), Box<dyn Error>> {
409    /// let epub = Epub::open("tests/ebooks/example_epub")?;
410    /// let mut reader = epub.reader();
411    ///
412    /// let content_a: String = reader.get(2)?.into();
413    /// let content_b = reader.get(2)?.into_string();
414    ///
415    /// assert_eq!(content_a, content_b);
416    /// # Ok(())
417    /// # }
418    /// ```
419    fn into_string(self) -> String {
420        self.into()
421    }
422
423    /// Takes the contained readable content bytes.
424    ///
425    /// This method is equivalent to calling `into::<Vec<u8>()`.
426    ///
427    /// # Examples:
428    /// - Extracting the contained content in the form of bytes:
429    /// ```
430    /// # use rbook::{Ebook, Epub};
431    /// # use rbook::reader::{Reader, ReaderContent};
432    /// # use std::error::Error;
433    /// # fn main() -> Result<(), Box<dyn Error>> {
434    /// let epub = Epub::open("tests/ebooks/example_epub")?;
435    /// let mut reader = epub.reader();
436    ///
437    /// let content_a: Vec<u8> = reader.get(2)?.into();
438    /// let content_b = reader.get(2)?.into_bytes();
439    ///
440    /// assert_eq!(content_a, content_b);
441    /// # Ok(())
442    /// # }
443    /// ```
444    fn into_bytes(self) -> Vec<u8> {
445        self.into()
446    }
447}
448
449/// A key to access content within a [`Reader`].
450#[non_exhaustive]
451#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
452pub enum ReaderKey<'a> {
453    /// A string value, intended for lookup within a [`Reader`].
454    ///
455    /// For an [`Epub`](crate::Epub), this value corresponds to the `idref` of a spine entry.
456    Value(&'a str),
457
458    /// An absolute position within the internal buffer of a [`Reader`].
459    ///
460    /// When passed as an argument to a reader,
461    /// it must be less than [`Reader::len`] or
462    /// [`ReaderError::OutOfBounds`](errors::ReaderError::OutOfBounds) will be returned.
463    Position(usize),
464}
465
466impl<'a> From<&'a str> for ReaderKey<'a> {
467    fn from(value: &'a str) -> Self {
468        Self::Value(value)
469    }
470}
471
472impl From<usize> for ReaderKey<'_> {
473    fn from(index: usize) -> Self {
474        Self::Position(index)
475    }
476}