Skip to main content

termdoc_core/
traits.rs

1//! The interfaces that decouple the pipeline.
2//!
3//! Every stage of docs/DESIGN.md ยง2.1 is a trait here. Readers live in crates that depend
4//! only on this one; backends live in crates that cannot see readers. Cargo's dependency
5//! graph is what keeps that separation from eroding.
6
7use crate::{Detection, Events, FormatId, Line, Result, Source};
8
9/// Turns bytes into the internal model. This is what the original design called a
10/// "Renderer": it produces the document, it does not paint it.
11pub trait DocumentReader: Send + Sync {
12    fn id(&self) -> FormatId;
13
14    /// Reads `src` and returns the stream. The `'a` lifetime ties the events to the
15    /// source, which is what lets `Cow::Borrowed` borrow from the `mmap` without copying.
16    fn read<'a>(&self, src: &'a Source, ctx: &ReadContext) -> Result<Events<'a>>;
17
18    fn capabilities(&self) -> ReaderCaps {
19        ReaderCaps::default()
20    }
21}
22
23/// What a reader can do. The CLI consults this to know, for instance, whether offering
24/// `--page` makes any sense.
25#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
26pub struct ReaderCaps {
27    /// Emits events without materializing the whole document.
28    pub streaming: bool,
29    /// Has navigable pages.
30    pub paginated: bool,
31    /// Can contribute metadata.
32    pub metadata: bool,
33}
34
35/// Options a reader needs to know about. Deliberately excludes width and color: those
36/// belong to the layout and the backend, and leaking them here would re-couple the
37/// stages.
38#[derive(Clone, Debug, Default)]
39pub struct ReadContext {
40    /// Page or section range requested with `--page`.
41    pub page_range: Option<(u32, u32)>,
42    /// Encoding forced with `--encoding`.
43    pub encoding: Option<String>,
44    /// Metadata only: lets the reader skip the body.
45    pub metadata_only: bool,
46}
47
48/// Proposes a format based on the source's prefix.
49pub trait Detector: Send + Sync {
50    fn sniff(&self, src: &Source) -> Option<Detection>;
51}
52
53/// Transforms the stream. These chain for free because they are iterator adapters.
54pub trait Transform: Send + Sync {
55    fn apply<'a>(&self, events: Events<'a>) -> Events<'a>;
56}
57
58/// Writes laid-out lines. Sees neither events nor readers.
59pub trait Backend {
60    fn caps(&self) -> BackendCaps;
61
62    fn begin(&mut self, out: &mut dyn std::io::Write) -> Result<()> {
63        let _ = out;
64        Ok(())
65    }
66
67    fn write_line(&mut self, line: &Line<'_>, out: &mut dyn std::io::Write) -> Result<()>;
68
69    fn finish(&mut self, out: &mut dyn std::io::Write) -> Result<()> {
70        let _ = out;
71        Ok(())
72    }
73}
74
75#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
76pub struct BackendCaps {
77    pub styled: bool,
78    pub hyperlinks: bool,
79    pub graphics: bool,
80}