assemble_core/flow/
shared.rs

1use 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
15/// Represents the artifact output of some task
16pub trait Artifact: Send + Sync {
17    /// The classifier of the artifact, if any
18    fn classifier(&self) -> Option<String>;
19    /// The date that should be used when publishing the artifact.
20    ///
21    /// By default, nothing is returned
22    fn date(&self) -> Option<Date> {
23        None
24    }
25    /// The extension of this published artifact
26    fn extension(&self) -> String;
27    /// The name of the artifact
28    fn name(&self) -> String;
29    /// The type of the artifact.
30    ///
31    /// By default, this value is the name as the extension but can have a different value.
32    fn artifact_type(&self) -> String;
33
34    /// Gets the file for this artifact.
35    ///
36    /// By default, this value is `[name][-[classifier]].[extension]`
37    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/// A configurable artifact.
120#[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    /// Set the name of the artifact
180    pub fn set_name(&mut self, name: impl AsRef<str>) {
181        self.name = name.as_ref().to_string();
182    }
183
184    /// Set the classifier of the artifact
185    pub fn set_classifier(&mut self, classifier: impl AsRef<str>) {
186        self.classifier = Some(classifier.as_ref().to_string());
187    }
188    /// Set the extension of the artifact
189    pub fn set_extension(&mut self, extension: impl AsRef<str>) {
190        self.extension = extension.as_ref().to_string();
191    }
192
193    /// Set the artifact's type
194    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    /// Set the file of the artifact
199    pub fn set_file(&mut self, file: PathBuf) {
200        self.file = Some(file);
201    }
202
203    /// Register some buildable that build this artifact
204    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
250/// Get access to some object's artifact
251pub trait IntoArtifact {
252    type IntoArtifact: Artifact;
253
254    /// Get an artifact from some type
255    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}