asciidork_parser/tasks/directives/includes/
include_resolver.rs

1use std::{any::Any, fmt};
2
3use crate::internal::*;
4
5#[derive(Debug, Eq, PartialEq, Clone)]
6pub enum IncludeTarget {
7  FilePath(String),
8  Uri(String),
9}
10
11impl IncludeTarget {
12  pub const fn is_path(&self) -> bool {
13    matches!(self, IncludeTarget::FilePath(_))
14  }
15
16  pub const fn is_uri(&self) -> bool {
17    matches!(self, IncludeTarget::Uri(_))
18  }
19
20  pub fn path(&self) -> Path {
21    match self {
22      IncludeTarget::FilePath(path) => Path::new(path),
23      IncludeTarget::Uri(uri) => Path::new(uri),
24    }
25  }
26}
27
28impl From<Path> for IncludeTarget {
29  fn from(path: Path) -> Self {
30    if path.is_uri() {
31      IncludeTarget::Uri(path.to_string())
32    } else {
33      IncludeTarget::FilePath(path.to_string())
34    }
35  }
36}
37
38pub trait IncludeResolver: Any {
39  fn resolve(
40    &mut self,
41    target: IncludeTarget,
42    buffer: &mut dyn IncludeBuffer,
43  ) -> std::result::Result<usize, ResolveError>;
44
45  fn get_base_dir(&self) -> Option<String> {
46    None
47  }
48  fn clone_box(&self) -> Box<dyn IncludeResolver>;
49}
50
51pub trait IncludeBuffer {
52  fn as_bytes_mut(&mut self) -> &mut [u8];
53  fn initialize(&mut self, len: usize);
54}
55
56impl IncludeBuffer for Vec<u8> {
57  fn initialize(&mut self, len: usize) {
58    self.reserve(len + 1); // for possible extra newline
59    self.resize(len, 0);
60  }
61
62  fn as_bytes_mut(&mut self) -> &mut [u8] {
63    self
64  }
65}
66
67impl IncludeBuffer for BumpVec<'_, u8> {
68  fn initialize(&mut self, len: usize) {
69    self.reserve(len + 1); // for possible extra newline
70    self.resize(len, 0);
71  }
72
73  fn as_bytes_mut(&mut self) -> &mut [u8] {
74    self
75  }
76}
77
78#[derive(Debug, Clone, PartialEq, Eq)]
79pub enum ResolveError {
80  NotFound,
81  Io(String),
82  UriReadNotSupported,
83  UriRead(String),
84  BaseDirRequired,
85  CaseMismatch(Option<String>),
86}
87
88impl fmt::Display for ResolveError {
89  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90    match self {
91      ResolveError::NotFound => write!(f, "File not found"),
92      ResolveError::Io(e) => write!(f, "I/O error: {e}"),
93      ResolveError::UriReadNotSupported => write!(f, "URI read not supported"),
94      ResolveError::UriRead(e) => write!(f, "Error reading URI: {e}"),
95      ResolveError::CaseMismatch(Some(path)) => {
96        write!(
97          f,
98          "Case mismatch in file path. Maybe you meant to include `{path}`?",
99        )
100      }
101      ResolveError::CaseMismatch(None) => write!(f, "Case mismatch in file path"),
102      ResolveError::BaseDirRequired => {
103        write!(
104          f,
105          "Include resolvers must supply a base_dir for relative includes from primary document"
106        )
107      }
108    }
109  }
110}
111
112impl From<std::io::Error> for ResolveError {
113  fn from(e: std::io::Error) -> Self {
114    ResolveError::Io(e.to_string())
115  }
116}
117
118// test helpers
119
120#[cfg(debug_assertions)]
121#[derive(Clone)]
122pub struct ConstResolver(pub Vec<u8>);
123#[cfg(debug_assertions)]
124impl IncludeResolver for ConstResolver {
125  fn resolve(
126    &mut self,
127    _: IncludeTarget,
128    buffer: &mut dyn IncludeBuffer,
129  ) -> std::result::Result<usize, ResolveError> {
130    buffer.initialize(self.0.len());
131    let bytes = buffer.as_bytes_mut();
132    bytes.copy_from_slice(&self.0);
133    Ok(self.0.len())
134  }
135
136  fn get_base_dir(&self) -> Option<String> {
137    Some("/".to_string())
138  }
139  fn clone_box(&self) -> Box<dyn IncludeResolver> {
140    Box::new(self.clone())
141  }
142}
143
144#[cfg(debug_assertions)]
145#[derive(Clone)]
146pub struct ErrorResolver(pub ResolveError);
147#[cfg(debug_assertions)]
148impl IncludeResolver for ErrorResolver {
149  fn resolve(
150    &mut self,
151    _: IncludeTarget,
152    _: &mut dyn IncludeBuffer,
153  ) -> std::result::Result<usize, ResolveError> {
154    Err(self.0.clone())
155  }
156
157  fn get_base_dir(&self) -> Option<String> {
158    Some("/".to_string())
159  }
160  fn clone_box(&self) -> Box<dyn IncludeResolver> {
161    Box::new(self.clone())
162  }
163}