aldrin_parser/resolver/
fs.rs

1use super::{Resolver, SchemaFile};
2use std::borrow::Cow;
3use std::collections::hash_map::{Entry, HashMap};
4use std::fs;
5use std::io::Error;
6use std::path::{Path, PathBuf};
7
8#[derive(Debug)]
9pub struct FilesystemResolver {
10    include_paths: Vec<PathBuf>,
11    main_schema: Schema,
12    schemas: HashMap<String, Schema>,
13}
14
15impl FilesystemResolver {
16    pub fn new(main_schema: impl AsRef<Path>) -> Self {
17        Self {
18            include_paths: Vec::new(),
19            main_schema: Schema::open(main_schema.as_ref()),
20            schemas: HashMap::new(),
21        }
22    }
23
24    pub fn with_include_paths<T>(main_schema: impl AsRef<Path>, include_paths: T) -> Self
25    where
26        T: IntoIterator,
27        T::Item: Into<PathBuf>,
28    {
29        let mut this = Self::new(main_schema);
30        this.add_include_paths(include_paths);
31        this
32    }
33
34    pub fn add_include_path(&mut self, include_path: impl Into<PathBuf>) -> &mut Self {
35        self.include_paths.push(include_path.into());
36        self
37    }
38
39    pub fn add_include_paths<T>(&mut self, include_paths: T) -> &mut Self
40    where
41        T: IntoIterator,
42        T::Item: Into<PathBuf>,
43    {
44        self.include_paths
45            .extend(include_paths.into_iter().map(Into::into));
46        self
47    }
48}
49
50impl Resolver for FilesystemResolver {
51    fn main_schema(&self) -> SchemaFile<'_> {
52        self.main_schema.as_schema_file()
53    }
54
55    fn resolve(&mut self, name: &str) -> Option<SchemaFile<'_>> {
56        if name == self.main_schema.name {
57            return Some(self.main_schema.as_schema_file());
58        }
59
60        let entry = match self.schemas.entry(name.to_owned()) {
61            Entry::Occupied(entry) => return Some(entry.into_mut().as_schema_file()),
62            Entry::Vacant(entry) => entry,
63        };
64
65        let path = self
66            .include_paths
67            .iter()
68            .rev()
69            .cloned()
70            .find_map(|mut path| {
71                path.push(name);
72                path.set_extension("aldrin");
73
74                if path.is_file() {
75                    Some(path)
76                } else {
77                    None
78                }
79            })?;
80
81        let schema = Schema::open_with_name(name.to_owned(), &path);
82        let schema = entry.insert(schema);
83        Some(schema.as_schema_file())
84    }
85}
86
87#[derive(Debug)]
88struct Schema {
89    name: String,
90    path: String,
91    source: Result<String, Error>,
92}
93
94impl Schema {
95    fn open(path: &Path) -> Self {
96        let name = Self::schema_name_from_path(path).into_owned();
97        Self::open_with_name(name, path)
98    }
99
100    fn open_with_name(name: String, path: &Path) -> Self {
101        Self {
102            name,
103            path: path.to_string_lossy().into_owned(),
104            source: fs::read_to_string(path),
105        }
106    }
107
108    fn schema_name_from_path(path: &Path) -> Cow<'_, str> {
109        match path.file_stem() {
110            Some(stem) => stem.to_string_lossy(),
111            None => Cow::Borrowed(""),
112        }
113    }
114
115    fn as_schema_file(&self) -> SchemaFile<'_> {
116        SchemaFile::new(&self.name, &self.path, self.source.as_deref())
117    }
118}