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}