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