Skip to main content

module_util/file/
mod.rs

1//! The [`File`] evaluator for working with modules from files.
2
3use std::mem::take;
4use std::path::PathBuf;
5use std::vec::Vec;
6
7use module::merge::Merge;
8use serde::Deserialize;
9
10use crate::evaluator::dfs;
11use crate::evaluator::{Acyclic, Dfs, Evaluator, Imports, Trace};
12
13pub mod format;
14pub use self::format::{Format, Module};
15
16pub mod error;
17pub use self::error::Error;
18
19///////////////////////////////////////////////////////////////////////////////
20
21/// The [`File`] evaluator.
22///
23/// An [`Evaluator`] accompanied by its driving logic. If you are trying to use
24/// this crate, this is probably where you should be looking.
25///
26/// # Example
27///
28/// ```rust,no_run
29/// # use std::path::PathBuf;
30/// # use module_util::file::{File, format};
31/// #[derive(Debug, PartialEq, Eq, serde::Deserialize, module::Merge)]
32/// struct MyModule {
33///     a: Option<i32>,
34///     b: Option<Vec<usize>>,
35/// }
36///
37/// let mut file: File<MyModule, format::Json> = File::default();
38///
39/// file.read(PathBuf::from("./module1.json")).unwrap();
40/// file.read(PathBuf::from("./module2.json")).unwrap();
41///
42/// assert_eq!(file.finish(), Some(MyModule {
43///     a: Some(42),
44///     b: Some(vec![0, 1, 2, 3])
45/// }));
46/// ```
47#[derive(Debug)]
48pub struct File<T, F>
49where
50    T: Merge,
51{
52    evaluator: Acyclic<Dfs<PathBuf, T>>,
53    format: F,
54}
55
56impl<T, F> File<T, F>
57where
58    T: Merge,
59{
60    /// Create a new [`File`] evaluator that reads modules based on `format`.
61    ///
62    /// See: [`Format`].
63    pub fn new(format: F) -> Self {
64        Self {
65            evaluator: Default::default(),
66            format,
67        }
68    }
69}
70
71impl<T, F> Default for File<T, F>
72where
73    T: Merge,
74    F: Default,
75{
76    fn default() -> Self {
77        Self::new(Default::default())
78    }
79}
80
81impl<T, F> File<T, F>
82where
83    T: Merge,
84    F: Format<T>,
85    F::Error: Send + Sync,
86{
87    /// Read the module from the file at `path`.
88    ///
89    /// This method will evaluate `path` along with all of its imports. It
90    /// should be used to declare only the root of the module tree.
91    ///
92    /// # Example
93    ///
94    /// See: [`File`].
95    pub fn read(&mut self, path: PathBuf) -> Result<(), Error> {
96        self.drive_to_end()?;
97        self.eval_one(path)?;
98        self.drive_to_end()?;
99        Ok(())
100    }
101
102    /// Destruct the evaluator and get the final value.
103    ///
104    /// # Example
105    ///
106    /// See: [`File`].
107    pub fn finish(self) -> Option<T> {
108        self.evaluator.finish()
109    }
110
111    fn eval_one(&mut self, path: PathBuf) -> Result<(), Error> {
112        let Module { imports, body } = self.format.read(&path).map_err(|e| {
113            Error::new(dfs::Error::other(e)).with_trace({
114                let mut trace = self.take_trace();
115
116                // We have to push here since the underlying evaluator has not seen
117                // this module yet.
118                trace.push(path.clone());
119                trace
120            })
121        })?;
122
123        let dirname = path
124            .parent()
125            .expect("read() succeeded so it should have a parent");
126
127        let imports = {
128            let mut tmp = Vec::from(imports);
129            for import in &mut tmp {
130                *import = dirname.join(&import);
131            }
132
133            Imports::from(tmp)
134        };
135
136        self.evaluator
137            .eval(path, imports, body)
138            .map_err(|e| Error::new(e).with_trace(self.take_trace()))?;
139
140        Ok(())
141    }
142
143    fn drive_to_end(&mut self) -> Result<(), Error> {
144        while let Some(path) = self.evaluator.next() {
145            self.eval_one(path)?;
146        }
147
148        Ok(())
149    }
150
151    fn take_trace(&mut self) -> Trace<PathBuf> {
152        let dfs = self.evaluator.get_mut();
153        take(&mut dfs.trace)
154    }
155}
156
157///////////////////////////////////////////////////////////////////////////////
158
159#[cfg(feature = "json")]
160/// Evaluate `paths` using [`File`] and the [`Json`] format.
161///
162/// This is a convenience function and does no magic. Returns `Ok(None)` only if
163/// `paths` is empty.
164///
165/// [`Json`]: format::Json
166pub fn json<T, I>(paths: I) -> Result<Option<T>, Error>
167where
168    T: for<'de> Deserialize<'de> + Merge,
169    I: IntoIterator<Item = PathBuf>,
170{
171    let mut file = File::new(format::Json);
172
173    for path in paths {
174        file.read(path)?;
175    }
176
177    Ok(file.finish())
178}
179
180///////////////////////////////////////////////////////////////////////////////
181
182#[cfg(feature = "toml")]
183/// Evaluate `paths` using [`File`] and the [`Toml`] format.
184///
185/// This is a convenience function and does no magic. Returns `Ok(None)` only if
186/// `paths` is empty.
187///
188/// [`Toml`]: format::Toml
189pub fn toml<T, I>(paths: I) -> Result<Option<T>, Error>
190where
191    T: for<'de> Deserialize<'de> + Merge,
192    I: IntoIterator<Item = PathBuf>,
193{
194    let mut file = File::new(format::Toml);
195
196    for path in paths {
197        file.read(path)?;
198    }
199
200    Ok(file.finish())
201}