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}