asciidork_parser/tasks/directives/includes/
include_resolver.rs1use 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); 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); 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#[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}