xidl_parser/hir/
include.rs1use crate::parser::IncludeResolver;
2use std::collections::BTreeMap;
3use std::fs;
4use std::path::{Path, PathBuf};
5
6pub struct FsIncludeResolver;
8
9impl IncludeResolver for FsIncludeResolver {
10 fn resolve(
11 &mut self,
12 parent_path: Option<&str>,
13 path: &str,
14 ) -> crate::error::ParserResult<(String, String)> {
15 let parent = parent_path.map(Path::new).unwrap_or_else(|| Path::new("."));
16 let base = parent.parent().unwrap_or_else(|| Path::new("."));
17 let resolved_path = normalize_path(&base.join(path));
18 read_include(&resolved_path)
19 }
20}
21
22pub struct MountIncludeResolver {
29 mounts: BTreeMap<String, PathBuf>,
30}
31
32impl MountIncludeResolver {
33 pub fn new(mounts: BTreeMap<String, PathBuf>) -> Self {
35 Self { mounts }
36 }
37
38 fn mount_for(&self, path: &str) -> Option<(&str, &Path)> {
40 self.mounts
41 .iter()
42 .filter(|(name, _)| {
43 path.strip_prefix(name.as_str())
44 .is_some_and(|rest| rest.is_empty() || rest.starts_with('/'))
45 })
46 .max_by_key(|(name, _)| name.len())
47 .map(|(name, root)| (name.as_str(), root.as_path()))
48 }
49}
50
51impl IncludeResolver for MountIncludeResolver {
52 fn resolve(
53 &mut self,
54 parent_path: Option<&str>,
55 path: &str,
56 ) -> crate::error::ParserResult<(String, String)> {
57 let Some((name, root)) = self.mount_for(path) else {
58 return FsIncludeResolver.resolve(parent_path, path);
59 };
60 let rest = path[name.len()..].trim_start_matches('/');
61 let resolved_path = normalize_path(&root.join(rest));
62 if !resolved_path.is_file() {
63 return Err(crate::error::ParseError::Message(format!(
64 "dependency include '{path}' does not exist: the '{name}' mount at '{}' \
65 resolves it to '{}'",
66 root.display(),
67 resolved_path.display()
68 )));
69 }
70 read_include(&resolved_path).map_err(|err| {
71 crate::error::ParseError::Message(format!(
72 "dependency include '{path}' could not be read through the '{name}' mount \
73 at '{}': {err}",
74 root.display()
75 ))
76 })
77 }
78}
79
80fn read_include(resolved_path: &Path) -> crate::error::ParserResult<(String, String)> {
82 if !resolved_path.is_file() {
83 return Err(crate::error::ParseError::Message(format!(
84 "include path '{}' does not exist",
85 resolved_path.display()
86 )));
87 }
88
89 let content = fs::read_to_string(resolved_path).map_err(|err| {
90 crate::error::ParseError::Message(format!(
91 "failed to read include '{}': {err}",
92 resolved_path.display()
93 ))
94 })?;
95
96 Ok((resolved_path.display().to_string(), content))
97}
98
99pub(crate) fn normalize_path(path: &Path) -> PathBuf {
100 let path = if path.is_absolute() {
101 path.to_path_buf()
102 } else {
103 std::env::current_dir()
104 .unwrap_or_else(|_| PathBuf::from("."))
105 .join(path)
106 };
107 fs::canonicalize(&path).unwrap_or(path)
108}