Skip to main content

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() { Some(path) } else { None }
75            })?;
76
77        let schema = Schema::open_with_name(name.to_owned(), &path);
78        let schema = entry.insert(schema);
79        Some(schema.as_schema_file())
80    }
81}
82
83#[derive(Debug)]
84struct Schema {
85    name: String,
86    path: String,
87    source: Result<String, Error>,
88}
89
90impl Schema {
91    fn open(path: &Path) -> Self {
92        let name = Self::schema_name_from_path(path).into_owned();
93        Self::open_with_name(name, path)
94    }
95
96    fn open_with_name(name: String, path: &Path) -> Self {
97        Self {
98            name,
99            path: path.to_string_lossy().into_owned(),
100            source: fs::read_to_string(path),
101        }
102    }
103
104    fn schema_name_from_path(path: &Path) -> Cow<'_, str> {
105        match path.file_stem() {
106            Some(stem) => stem.to_string_lossy(),
107            None => Cow::Borrowed(""),
108        }
109    }
110
111    fn as_schema_file(&self) -> SchemaFile<'_> {
112        SchemaFile::new(&self.name, &self.path, self.source.as_deref())
113    }
114}