asciidork_parser/tasks/directives/includes/
include_resolver.rs

1use std::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 {
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
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  CaseMismatch(Option<String>),
87}
88
89impl fmt::Display for ResolveError {
90  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91    match self {
92      ResolveError::NotFound => write!(f, "File not found"),
93      ResolveError::Io(e) => write!(f, "I/O error: {}", e),
94      ResolveError::UriReadNotSupported => write!(f, "URI read not supported"),
95      ResolveError::UriRead(e) => write!(f, "Error reading URI: {}", e),
96      ResolveError::CaseMismatch(Some(path)) => {
97        write!(
98          f,
99          "Case mismatch in file path. Maybe you meant to include `{path}`?",
100        )
101      }
102      ResolveError::CaseMismatch(None) => write!(f, "Case mismatch in file path"),
103      ResolveError::BaseDirRequired => {
104        write!(
105          f,
106          "Include resolvers must supply a base_dir for relative includes from primary document"
107        )
108      }
109    }
110  }
111}
112
113impl From<std::io::Error> for ResolveError {
114  fn from(e: std::io::Error) -> Self {
115    ResolveError::Io(e.to_string())
116  }
117}
118
119// test helpers
120
121#[cfg(debug_assertions)]
122#[derive(Clone)]
123pub struct ConstResolver(pub Vec<u8>);
124#[cfg(debug_assertions)]
125impl IncludeResolver for ConstResolver {
126  fn resolve(
127    &mut self,
128    _: IncludeTarget,
129    buffer: &mut dyn IncludeBuffer,
130  ) -> std::result::Result<usize, ResolveError> {
131    buffer.initialize(self.0.len());
132    let bytes = buffer.as_bytes_mut();
133    bytes.copy_from_slice(&self.0);
134    Ok(self.0.len())
135  }
136
137  fn get_base_dir(&self) -> Option<String> {
138    Some("/".to_string())
139  }
140  fn clone_box(&self) -> Box<dyn IncludeResolver> {
141    Box::new(self.clone())
142  }
143}
144
145#[cfg(debug_assertions)]
146#[derive(Clone)]
147pub struct ErrorResolver(pub ResolveError);
148#[cfg(debug_assertions)]
149impl IncludeResolver for ErrorResolver {
150  fn resolve(
151    &mut self,
152    _: IncludeTarget,
153    _: &mut dyn IncludeBuffer,
154  ) -> std::result::Result<usize, ResolveError> {
155    Err(self.0.clone())
156  }
157
158  fn get_base_dir(&self) -> Option<String> {
159    Some("/".to_string())
160  }
161  fn clone_box(&self) -> Box<dyn IncludeResolver> {
162    Box::new(self.clone())
163  }
164}