Skip to main content

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    safe_mode: SafeMode,
44  ) -> std::result::Result<usize, ResolveError>;
45
46  fn get_base_dir(&self) -> Option<String> {
47    None
48  }
49  fn clone_box(&self) -> Box<dyn IncludeResolver>;
50}
51
52pub trait IncludeBuffer {
53  fn as_bytes_mut(&mut self) -> &mut [u8];
54  fn initialize(&mut self, len: usize);
55}
56
57impl IncludeBuffer for Vec<u8> {
58  fn initialize(&mut self, len: usize) {
59    self.reserve(len + 1); // for possible extra newline
60    self.resize(len, 0);
61  }
62
63  fn as_bytes_mut(&mut self) -> &mut [u8] {
64    self
65  }
66}
67
68impl IncludeBuffer for BumpVec<'_, u8> {
69  fn initialize(&mut self, len: usize) {
70    self.reserve(len + 1); // for possible extra newline
71    self.resize(len, 0);
72  }
73
74  fn as_bytes_mut(&mut self) -> &mut [u8] {
75    self
76  }
77}
78
79#[derive(Debug, Clone, PartialEq, Eq)]
80pub enum ResolveError {
81  NotFound,
82  Io(String),
83  UriReadNotSupported,
84  UriRead(String),
85  BaseDirRequired,
86  RestrictedPath,
87  CaseMismatch(Option<String>),
88}
89
90impl fmt::Display for ResolveError {
91  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92    match self {
93      ResolveError::NotFound => write!(f, "File not found"),
94      ResolveError::Io(e) => write!(f, "I/O error: {e}"),
95      ResolveError::UriReadNotSupported => write!(f, "URI read not supported"),
96      ResolveError::UriRead(e) => write!(f, "Error reading URI: {e}"),
97      ResolveError::CaseMismatch(Some(path)) => {
98        write!(
99          f,
100          "Case mismatch in file path. Maybe you meant to include `{path}`?",
101        )
102      }
103      ResolveError::CaseMismatch(None) => write!(f, "Case mismatch in file path"),
104      ResolveError::BaseDirRequired => {
105        write!(
106          f,
107          "Include resolvers must supply a base_dir for relative includes from primary document"
108        )
109      }
110      ResolveError::RestrictedPath => {
111        write!(f, "Include path outside docdir restricted by safe mode")
112      }
113    }
114  }
115}
116
117impl From<std::io::Error> for ResolveError {
118  fn from(e: std::io::Error) -> Self {
119    ResolveError::Io(e.to_string())
120  }
121}
122
123// test helpers
124
125#[cfg(debug_assertions)]
126#[derive(Clone)]
127pub struct ConstResolver(pub Vec<u8>);
128#[cfg(debug_assertions)]
129impl IncludeResolver for ConstResolver {
130  fn resolve(
131    &mut self,
132    _: IncludeTarget,
133    buffer: &mut dyn IncludeBuffer,
134    _: SafeMode,
135  ) -> std::result::Result<usize, ResolveError> {
136    buffer.initialize(self.0.len());
137    let bytes = buffer.as_bytes_mut();
138    bytes.copy_from_slice(&self.0);
139    Ok(self.0.len())
140  }
141
142  fn get_base_dir(&self) -> Option<String> {
143    Some("/".to_string())
144  }
145  fn clone_box(&self) -> Box<dyn IncludeResolver> {
146    Box::new(self.clone())
147  }
148}
149
150#[cfg(debug_assertions)]
151#[derive(Clone)]
152pub struct ErrorResolver(pub ResolveError);
153#[cfg(debug_assertions)]
154impl IncludeResolver for ErrorResolver {
155  fn resolve(
156    &mut self,
157    _: IncludeTarget,
158    _: &mut dyn IncludeBuffer,
159    _: SafeMode,
160  ) -> std::result::Result<usize, ResolveError> {
161    Err(self.0.clone())
162  }
163
164  fn get_base_dir(&self) -> Option<String> {
165    Some("/".to_string())
166  }
167  fn clone_box(&self) -> Box<dyn IncludeResolver> {
168    Box::new(self.clone())
169  }
170}