assemble_core/flow/
shared.rs1use crate::__export::TaskId;
2
3use crate::file::RegularFile;
4use crate::prelude::ProjectResult;
5use crate::project::buildable::{Buildable, BuiltByContainer, IntoBuildable};
6
7use crate::Project;
8use std::collections::HashSet;
9use std::fmt::{Debug, Formatter};
10use std::hash::{Hash, Hasher};
11use std::path::{Path, PathBuf};
12use std::sync::Arc;
13use time::Date;
14
15pub trait Artifact: Send + Sync {
17 fn classifier(&self) -> Option<String>;
19 fn date(&self) -> Option<Date> {
23 None
24 }
25 fn extension(&self) -> String;
27 fn name(&self) -> String;
29 fn artifact_type(&self) -> String;
33
34 fn file(&self) -> PathBuf {
38 default_file(self)
39 }
40
41 fn buildable(&self) -> Option<Box<dyn Buildable>> {
42 None
43 }
44}
45
46impl Artifact for Arc<dyn Artifact> {
47 fn classifier(&self) -> Option<String> {
48 (**self).classifier()
49 }
50
51 fn date(&self) -> Option<Date> {
52 (**self).date()
53 }
54
55 fn extension(&self) -> String {
56 (**self).extension()
57 }
58
59 fn name(&self) -> String {
60 (**self).name()
61 }
62
63 fn artifact_type(&self) -> String {
64 (**self).artifact_type()
65 }
66
67 fn file(&self) -> PathBuf {
68 (**self).file()
69 }
70
71 fn buildable(&self) -> Option<Box<dyn Buildable>> {
72 (**self).buildable()
73 }
74}
75
76impl Artifact for Box<dyn Artifact> {
77 fn classifier(&self) -> Option<String> {
78 (**self).classifier()
79 }
80
81 fn date(&self) -> Option<Date> {
82 (**self).date()
83 }
84
85 fn extension(&self) -> String {
86 (**self).extension()
87 }
88
89 fn name(&self) -> String {
90 (**self).name()
91 }
92
93 fn artifact_type(&self) -> String {
94 (**self).artifact_type()
95 }
96
97 fn file(&self) -> PathBuf {
98 (**self).file()
99 }
100
101 fn buildable(&self) -> Option<Box<dyn Buildable>> {
102 (**self).buildable()
103 }
104}
105
106fn default_file<A: Artifact + ?Sized>(artifact: &A) -> PathBuf {
107 let as_string = format!(
108 "{}{}.{}",
109 artifact.name(),
110 artifact
111 .classifier()
112 .map(|s| format!("-{}", s))
113 .unwrap_or_default(),
114 artifact.extension()
115 );
116 PathBuf::from(as_string)
117}
118
119#[derive(Clone)]
121pub struct ConfigurableArtifact {
122 classifier: Option<String>,
123 name: String,
124 extension: String,
125 artifact_type: Option<String>,
126 built_by: BuiltByContainer,
127 file: Option<PathBuf>,
128}
129
130impl PartialEq for ConfigurableArtifact {
131 fn eq(&self, other: &Self) -> bool {
132 self.classifier == other.classifier
133 && self.name == other.name
134 && self.extension == other.extension
135 && self.artifact_type == other.artifact_type
136 }
137}
138
139impl Eq for ConfigurableArtifact {}
140
141impl Hash for ConfigurableArtifact {
142 fn hash<H: Hasher>(&self, state: &mut H) {
143 self.classifier.hash(state);
144 self.name.hash(state);
145 self.extension.hash(state);
146 self.artifact_type.hash(state);
147 }
148}
149
150impl ConfigurableArtifact {
151 pub fn from_artifact<A: IntoArtifact>(artifact: A) -> Self
152 where
153 A::IntoArtifact: 'static,
154 {
155 let artifact = artifact.into_artifact();
156 let container = BuiltByContainer::new();
157
158 Self {
159 classifier: artifact.classifier(),
160 name: artifact.name(),
161 extension: artifact.extension(),
162 artifact_type: Some(artifact.artifact_type()),
163 built_by: container,
164 file: Some(artifact.file()),
165 }
166 }
167
168 pub fn new(name: String, extension: String) -> Self {
169 Self {
170 classifier: None,
171 name,
172 extension,
173 artifact_type: None,
174 built_by: BuiltByContainer::new(),
175 file: None,
176 }
177 }
178
179 pub fn set_name(&mut self, name: impl AsRef<str>) {
181 self.name = name.as_ref().to_string();
182 }
183
184 pub fn set_classifier(&mut self, classifier: impl AsRef<str>) {
186 self.classifier = Some(classifier.as_ref().to_string());
187 }
188 pub fn set_extension(&mut self, extension: impl AsRef<str>) {
190 self.extension = extension.as_ref().to_string();
191 }
192
193 pub fn set_artifact_type(&mut self, artifact_type: impl AsRef<str>) {
195 self.artifact_type = Some(artifact_type.as_ref().to_string());
196 }
197
198 pub fn set_file(&mut self, file: PathBuf) {
200 self.file = Some(file);
201 }
202
203 pub fn built_by<B: IntoBuildable>(&mut self, build: B)
205 where
206 B::Buildable: 'static,
207 {
208 self.built_by.add(build)
209 }
210}
211
212impl Buildable for ConfigurableArtifact {
213 fn get_dependencies(&self, project: &Project) -> ProjectResult<HashSet<TaskId>> {
214 self.built_by.get_dependencies(project)
215 }
216}
217
218impl Debug for ConfigurableArtifact {
219 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
220 write!(f, "{:?}", self.file())
221 }
222}
223
224impl Artifact for ConfigurableArtifact {
225 fn classifier(&self) -> Option<String> {
226 self.classifier.clone()
227 }
228
229 fn extension(&self) -> String {
230 self.extension.clone()
231 }
232
233 fn name(&self) -> String {
234 self.name.clone()
235 }
236
237 fn artifact_type(&self) -> String {
238 self.artifact_type.clone().unwrap_or(self.extension())
239 }
240
241 fn file(&self) -> PathBuf {
242 self.file.clone().unwrap_or_else(|| default_file(self))
243 }
244
245 fn buildable(&self) -> Option<Box<dyn Buildable>> {
246 Some(Box::new(self.built_by.clone()))
247 }
248}
249
250pub trait IntoArtifact {
252 type IntoArtifact: Artifact;
253
254 fn into_artifact(self) -> Self::IntoArtifact;
256}
257
258impl<A: Artifact> IntoArtifact for A {
259 type IntoArtifact = A;
260
261 fn into_artifact(self) -> Self::IntoArtifact {
262 self
263 }
264}
265
266impl IntoArtifact for PathBuf {
267 type IntoArtifact = ConfigurableArtifact;
268
269 fn into_artifact(self) -> Self::IntoArtifact {
270 self.as_path().into_artifact()
271 }
272}
273
274impl IntoArtifact for &Path {
275 type IntoArtifact = ConfigurableArtifact;
276
277 fn into_artifact(self) -> Self::IntoArtifact {
278 let name = self
279 .file_name()
280 .expect("no file name found")
281 .to_str()
282 .unwrap()
283 .to_string();
284
285 let mut artifact = if name.contains('.') {
286 let name = name.rsplit_once('.').unwrap().0.to_string();
287 let ext = self
288 .extension()
289 .expect("no extension found")
290 .to_str()
291 .unwrap()
292 .to_string();
293 ConfigurableArtifact::new(name, ext)
294 } else {
295 ConfigurableArtifact {
296 classifier: None,
297 name,
298 extension: "".to_string(),
299 artifact_type: Some("directory".to_string()),
300 built_by: Default::default(),
301 file: None,
302 }
303 };
304 artifact.set_file(self.to_path_buf());
305 artifact
306 }
307}
308
309impl IntoArtifact for RegularFile {
310 type IntoArtifact = ConfigurableArtifact;
311
312 fn into_artifact(self) -> Self::IntoArtifact {
313 self.path().into_artifact()
314 }
315}
316
317#[derive(Debug, Clone, Eq, PartialEq, Hash)]
318pub struct ImmutableArtifact {
319 classifier: Option<String>,
320 name: String,
321 extension: String,
322 artifact_type: String,
323 file: PathBuf,
324}
325
326impl ImmutableArtifact {
327 pub fn new<A: IntoArtifact>(artifact: A) -> Self {
328 let as_artifact = artifact.into_artifact();
329 Self {
330 classifier: as_artifact.classifier(),
331 name: as_artifact.name(),
332 extension: as_artifact.extension(),
333 artifact_type: as_artifact.artifact_type(),
334 file: as_artifact.file(),
335 }
336 }
337}
338
339impl Artifact for ImmutableArtifact {
340 fn classifier(&self) -> Option<String> {
341 self.classifier.clone()
342 }
343
344 fn extension(&self) -> String {
345 self.extension.clone()
346 }
347
348 fn name(&self) -> String {
349 self.name.clone()
350 }
351
352 fn artifact_type(&self) -> String {
353 self.artifact_type.clone()
354 }
355
356 fn file(&self) -> PathBuf {
357 self.file.clone()
358 }
359}
360
361#[cfg(test)]
362mod tests {
363 use crate::flow::shared::{Artifact, IntoArtifact};
364 use std::path::PathBuf;
365
366 #[test]
367 fn artifact_from_path() {
368 let path = PathBuf::from("artifact.zip");
369 let artifact = path.into_artifact();
370 assert_eq!(artifact.name(), "artifact");
371 assert_eq!(artifact.extension(), "zip");
372 }
373}