Skip to main content

rig_core/loaders/
mod.rs

1//! File loading utilities for preparing local documents as model or embedding input.
2//!
3//! [`FileLoader`] provides a common interface for reading files from disk, glob
4//! matches, directories, or in-memory bytes. It can return content alone or pair
5//! content with source paths, and it can optionally skip per-file errors.
6//!
7//! `PdfFileLoader` is available with the `pdf` feature. It loads PDFs and can
8//! split extracted text by page while preserving page numbers.
9//!
10//! `EpubFileLoader` is available with the `epub` feature. It loads EPUB files
11//! and can split extracted text by chapter while preserving chapter numbers.
12
13// ================================================================
14// Shared scaffolding for the typestate loaders (file, pdf, epub)
15// ================================================================
16
17/// Defines the `pub(crate)` source trait for a loader (e.g. `Readable` /
18/// `Loadable`) together with its blanket impl for `Result`, which lets loader
19/// states whose items are results be consumed transparently.
20macro_rules! loadable_trait {
21    ($Trait:ident, $Err:ty, $Out:ty, $get:ident, $get_with_path:ident) => {
22        pub(crate) trait $Trait {
23            fn $get(self) -> Result<$Out, $Err>;
24            fn $get_with_path(self) -> Result<(std::path::PathBuf, $Out), $Err>;
25        }
26
27        impl<T: $Trait> $Trait for Result<T, $Err> {
28            fn $get(self) -> Result<$Out, $Err> {
29                self.map(|t| t.$get())?
30            }
31            fn $get_with_path(self) -> Result<(std::path::PathBuf, $Out), $Err> {
32                self.map(|t| t.$get_with_path())?
33            }
34        }
35    };
36}
37
38/// The `with_dir` doc line matching each `loader_dir_entries!` kind.
39macro_rules! loader_dir_doc {
40    (files_only) => {
41        "Creates a new loader on all files within a directory (ignores subdirectories)."
42    };
43    (all_entries) => {
44        "Creates a new loader on all entries within a directory. Entries are not \
45         filtered: a non-file entry's path is yielded too and surfaces as an error \
46         when loaded."
47    };
48}
49
50/// Expands to the directory-entry iterator used by a loader's `with_dir`.
51///
52/// - `files_only`: skips unreadable entries and non-files (the [`file::FileLoader`]
53///   behavior).
54/// - `all_entries`: yields every entry's path, surfacing entry errors as items
55///   (the pdf/epub behavior).
56macro_rules! loader_dir_entries {
57    (files_only, $entries:expr, $Err:ty) => {
58        $entries.filter_map(|entry| {
59            let path = entry.ok()?.path();
60            if path.is_file() { Some(Ok(path)) } else { None }
61        })
62    };
63    (all_entries, $entries:expr, $Err:ty) => {
64        $entries.map(|entry| {
65            Ok(entry
66                .map_err($crate::loaders::file::FileLoaderError::IoError)
67                .map_err(<$Err>::from)?
68                .path())
69        })
70    };
71}
72
73/// Generates the shared typestate plumbing for a loader struct with a boxed
74/// `iterator` field: the `IntoIter` type with its `IntoIterator`/`Iterator`
75/// impls, `ignore_errors` on `Result` states, and the `with_glob`/`with_dir`
76/// constructors. Pass `extra: P` for loaders carrying an extra type parameter
77/// held in a `_processor: PhantomData<P>` field.
78macro_rules! loader_scaffold {
79    ($Loader:ident, $Err:ty, dir: $dir_kind:ident $(, extra: $P:ident)?) => {
80        pub struct IntoIter<'a, T> {
81            iterator: Box<dyn Iterator<Item = T> + 'a>,
82        }
83
84        impl<'a, T $(, $P)?> IntoIterator for $Loader<'a, T $(, $P)?> {
85            type Item = T;
86            type IntoIter = IntoIter<'a, T>;
87
88            fn into_iter(self) -> Self::IntoIter {
89                IntoIter {
90                    iterator: self.iterator,
91                }
92            }
93        }
94
95        impl<T> Iterator for IntoIter<'_, T> {
96            type Item = T;
97
98            fn next(&mut self) -> Option<Self::Item> {
99                self.iterator.next()
100            }
101        }
102
103        impl<'a, T: 'a $(, $P)?> $Loader<'a, Result<T, $Err> $(, $P)?> {
104            /// Ignores errors in the iterator, returning only successful results. This
105            ///  can be used on any loader state of iterator whose items are results.
106            pub fn ignore_errors(self) -> $Loader<'a, T $(, $P)?> {
107                $Loader {
108                    iterator: Box::new(self.iterator.filter_map(|res| res.ok())),
109                    $(_processor: std::marker::PhantomData::<$P>,)?
110                }
111            }
112        }
113
114        impl<'a $(, $P)?> $Loader<'a, Result<std::path::PathBuf, $Err> $(, $P)?> {
115            /// Creates a new loader using a glob pattern to match files.
116            pub fn with_glob(
117                pattern: &str,
118            ) -> Result<$Loader<'_, Result<std::path::PathBuf, $Err> $(, $P)?>, $Err> {
119                let paths = ::glob::glob(pattern)
120                    .map_err($crate::loaders::file::FileLoaderError::PatternError)
121                    .map_err(<$Err>::from)?;
122                Ok($Loader {
123                    iterator: Box::new(paths.map(|path| {
124                        path.map_err($crate::loaders::file::FileLoaderError::GlobError)
125                            .map_err(<$Err>::from)
126                    })),
127                    $(_processor: std::marker::PhantomData::<$P>,)?
128                })
129            }
130
131            #[doc = loader_dir_doc!($dir_kind)]
132            pub fn with_dir(
133                directory: &str,
134            ) -> Result<$Loader<'_, Result<std::path::PathBuf, $Err> $(, $P)?>, $Err> {
135                let entries = std::fs::read_dir(directory)
136                    .map_err($crate::loaders::file::FileLoaderError::IoError)
137                    .map_err(<$Err>::from)?;
138                Ok($Loader {
139                    iterator: Box::new(loader_dir_entries!($dir_kind, entries, $Err)),
140                    $(_processor: std::marker::PhantomData::<$P>,)?
141                })
142            }
143        }
144    };
145}
146
147/// Generates the byte-ingestion constructors for a loader (file and pdf).
148macro_rules! loader_from_bytes {
149    ($Loader:ident) => {
150        impl<'a> $Loader<'a, Vec<u8>> {
151            /// Ingest a document as a byte array.
152            pub fn from_bytes(bytes: Vec<u8>) -> $Loader<'a, Vec<u8>> {
153                $Loader {
154                    iterator: Box::new(vec![bytes].into_iter()),
155                }
156            }
157
158            /// Ingest multiple byte arrays.
159            pub fn from_bytes_multi(bytes_vec: Vec<Vec<u8>>) -> $Loader<'a, Vec<u8>> {
160                $Loader {
161                    iterator: Box::new(bytes_vec.into_iter()),
162                }
163            }
164        }
165    };
166}
167
168pub mod file;
169
170pub use file::FileLoader;
171
172// Test-only helpers for resolving on-disk fixtures in a CWD-independent way.
173// Gated to the features whose tests use them so it never warns as dead code.
174#[cfg(all(test, any(feature = "pdf", feature = "epub")))]
175mod test_fixtures;
176
177#[cfg(feature = "pdf")]
178#[cfg_attr(docsrs, doc(cfg(feature = "pdf")))]
179pub mod pdf;
180
181#[cfg(feature = "pdf")]
182pub use pdf::PdfFileLoader;
183
184#[cfg(feature = "epub")]
185#[cfg_attr(docsrs, doc(cfg(feature = "epub")))]
186pub mod epub;
187
188#[cfg(feature = "epub")]
189pub use epub::{EpubFileLoader, RawTextProcessor, StripXmlProcessor, TextProcessor};