1use crate::{Replace, Transcript};
4use relative_path::{RelativePath, RelativePathBuf};
5use std::slice;
6
7#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Deserialize, serde::Serialize)]
8pub struct ReplaceFile {
9 path: RelativePathBuf,
10 #[serde(default)]
12 #[serde(skip_serializing_if = "Option::is_none")]
13 transcript: Option<Transcript>,
14 #[serde(default)]
16 #[serde(skip_serializing_if = "Vec::is_empty")]
17 replace: Vec<Replace>,
18}
19
20#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Deserialize, serde::Serialize)]
21#[serde(untagged)]
22pub enum Files {
23 List(Vec<ReplaceFile>),
24 Map(linked_hash_map::LinkedHashMap<RelativePathBuf, Transcript>),
25 ListOfMaps(Vec<linked_hash_map::LinkedHashMap<RelativePathBuf, Transcript>>),
26}
27
28impl Files {
29 pub fn is_empty(&self) -> bool {
31 match *self {
32 Files::List(ref list) => list.is_empty(),
33 Files::Map(ref map) => map.is_empty(),
34 Files::ListOfMaps(ref list) => list.iter().all(|m| m.is_empty()),
35 }
36 }
37
38 pub fn iter(&self) -> FilesIter<'_> {
40 match *self {
41 Files::List(ref list) => FilesIter::List(list.iter()),
42 Files::Map(ref map) => FilesIter::Map(map.iter()),
43 Files::ListOfMaps(ref list) => FilesIter::ListOfMaps {
44 current: None,
45 it: list.iter(),
46 },
47 }
48 }
49
50 fn insert(&mut self, path: RelativePathBuf, transcript: Transcript) {
52 match *self {
53 Files::List(ref mut list) => list.push(ReplaceFile {
54 path,
55 transcript: Some(transcript),
56 replace: vec![],
57 }),
58 Files::Map(ref mut map) => {
59 map.insert(path, transcript);
60 }
61 Files::ListOfMaps(ref mut list) => {
62 let mut map = linked_hash_map::LinkedHashMap::new();
63 map.insert(path, transcript);
64 list.push(map);
65 }
66 }
67 }
68}
69
70impl Default for Files {
71 fn default() -> Self {
72 Files::ListOfMaps(vec![])
73 }
74}
75
76impl<'a> IntoIterator for &'a Files {
77 type IntoIter = FilesIter<'a>;
78 type Item = (&'a RelativePath, Vec<&'a Replace>, Option<&'a Transcript>);
79
80 fn into_iter(self) -> Self::IntoIter {
81 self.iter()
82 }
83}
84
85pub enum FilesIter<'a> {
87 List(slice::Iter<'a, ReplaceFile>),
88 Map(linked_hash_map::Iter<'a, RelativePathBuf, Transcript>),
89 ListOfMaps {
90 current: Option<linked_hash_map::Iter<'a, RelativePathBuf, Transcript>>,
91 it: slice::Iter<'a, linked_hash_map::LinkedHashMap<RelativePathBuf, Transcript>>,
92 },
93}
94
95impl<'a> Iterator for FilesIter<'a> {
96 type Item = (&'a RelativePath, Vec<&'a Replace>, Option<&'a Transcript>);
97
98 fn next(&mut self) -> Option<Self::Item> {
99 match *self {
100 FilesIter::List(ref mut it) => {
101 let ReplaceFile {
102 ref path,
103 ref transcript,
104 ref replace,
105 } = it.next()?;
106 Some((path, replace.iter().collect(), transcript.as_ref()))
107 }
108 FilesIter::Map(ref mut it) => {
109 let (ref path, ref transcript) = it.next()?;
110 Some((path, vec![], Some(transcript)))
111 }
112 FilesIter::ListOfMaps {
113 ref mut current,
114 ref mut it,
115 } => loop {
116 if let Some((ref path, ref transcript)) = current.as_mut().and_then(|it| it.next())
117 {
118 return Some((path, vec![], Some(transcript)));
119 }
120
121 *current = match it.next() {
122 Some(n) => Some(n.iter()),
123 None => return None,
124 }
125 },
126 }
127 }
128}
129
130#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Deserialize, serde::Serialize)]
131pub struct ReplaceDir {
132 pub path: RelativePathBuf,
133 #[serde(default)]
134 #[serde(rename = "file_prefix")]
135 #[serde(skip_serializing_if = "Option::is_none")]
136 pub prefix: Option<String>,
137 #[serde(default)]
138 #[serde(skip_serializing_if = "Option::is_none")]
139 pub suffix: Option<String>,
140 #[serde(default)]
141 #[serde(skip_serializing_if = "Option::is_none")]
142 pub file_extension: Option<String>,
143 #[serde(default)]
144 #[serde(skip_serializing_if = "Files::is_empty")]
145 pub files: Files,
146}
147
148impl ReplaceDir {
149 pub fn new(path: RelativePathBuf) -> Self {
151 ReplaceDir {
152 path,
153 prefix: None,
154 suffix: None,
155 file_extension: None,
156 files: Files::List(vec![]),
157 }
158 }
159
160 pub fn insert_file(
162 &mut self,
163 file_extension: Option<&str>,
164 mut file: RelativePathBuf,
165 transcript: Transcript,
166 ) -> Result<(), failure::Error> {
167 let file_extension = self
168 .file_extension
169 .as_ref()
170 .map(|s| s.as_str())
171 .or(file_extension);
172
173 if let Some(e) = file_extension {
174 if Some(e) != file.extension() {
175 failure::bail!("extension does not match");
176 }
177
178 file = match file.file_stem() {
179 Some(stem) => file.with_file_name(stem),
180 None => file,
181 };
182 }
183
184 if let Some(prefix) = self.prefix.as_ref() {
185 let mut name = match file.file_name() {
186 Some(name) => name,
187 None => failure::bail!("expected file name"),
188 };
189
190 if !name.starts_with(prefix) {
191 failure::bail!("bad prefix in file");
192 }
193
194 name = &name[prefix.len()..];
195 file = file.with_file_name(name);
196 }
197
198 if let Some(suffix) = self.suffix.as_ref() {
199 let mut name = match file.file_name() {
200 Some(name) => name,
201 None => failure::bail!("expected file name"),
202 };
203
204 if !name.ends_with(suffix) {
205 failure::bail!("bad prefix in file");
206 }
207
208 name = &name[..(name.len() - suffix.len())];
209 file = file.with_file_name(name);
210 }
211
212 self.files.insert(file, transcript);
213 Ok(())
214 }
215
216 pub fn contains(&self, path: &RelativePath) -> bool {
218 let stem = match path.file_stem() {
219 Some(stem) => stem,
220 None => return false,
221 };
222
223 if let Some(prefix) = self.prefix.as_ref() {
224 if !stem.starts_with(prefix) {
225 return false;
226 }
227 }
228
229 if let Some(suffix) = self.suffix.as_ref() {
230 if !stem.ends_with(suffix) {
231 return false;
232 }
233 }
234
235 if let Some(extension) = self.file_extension.as_ref() {
236 match path.extension() {
237 Some(e) if e == extension => {}
238 _ => return false,
239 }
240 }
241
242 true
243 }
244}
245
246#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
247pub struct Config {
248 #[serde(default)]
249 #[serde(skip_serializing_if = "Option::is_none")]
250 pub file_extension: Option<String>,
251 #[serde(default)]
252 #[serde(skip_serializing_if = "Vec::is_empty")]
253 pub dirs: Vec<ReplaceDir>,
254}
255
256impl Config {
257 pub fn insert_file<'a>(
259 &'a mut self,
260 file_dir: &RelativePath,
261 file: RelativePathBuf,
262 transcript: Transcript,
263 ) -> Result<(), failure::Error> {
264 let mut found = None;
265
266 for (i, dir) in self.dirs.iter().enumerate() {
267 if dir.path == file_dir && dir.contains(&file) {
268 found = Some(i);
269 break;
270 }
271 }
272
273 let i = match found {
274 Some(i) => i,
275 None => {
276 let mut dir = ReplaceDir::new(file_dir.to_owned());
277 dir.files = Files::ListOfMaps(vec![]);
278
279 let len = self.dirs.len();
280 self.dirs.push(dir);
281 len
282 }
283 };
284
285 let Config {
286 ref mut dirs,
287 ref file_extension,
288 ..
289 } = *self;
290
291 dirs[i].insert_file(
292 file_extension.as_ref().map(|s| s.as_str()),
293 file,
294 transcript,
295 )?;
296 Ok(())
297 }
298
299 pub fn optimize(&mut self) -> Result<(), failure::Error> {
301 self.dirs.sort();
302 Ok(())
303 }
304}