use crate::__export::TaskId;
use crate::file::RegularFile;
use crate::prelude::ProjectResult;
use crate::project::buildable::{Buildable, BuiltByContainer, IntoBuildable};
use crate::Project;
use std::collections::HashSet;
use std::fmt::{Debug, Formatter};
use std::hash::{Hash, Hasher};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use time::Date;
pub trait Artifact: Send + Sync {
fn classifier(&self) -> Option<String>;
fn date(&self) -> Option<Date> {
None
}
fn extension(&self) -> String;
fn name(&self) -> String;
fn artifact_type(&self) -> String;
fn file(&self) -> PathBuf {
default_file(self)
}
fn buildable(&self) -> Option<Box<dyn Buildable>> {
None
}
}
impl Artifact for Arc<dyn Artifact> {
fn classifier(&self) -> Option<String> {
(**self).classifier()
}
fn date(&self) -> Option<Date> {
(**self).date()
}
fn extension(&self) -> String {
(**self).extension()
}
fn name(&self) -> String {
(**self).name()
}
fn artifact_type(&self) -> String {
(**self).artifact_type()
}
fn file(&self) -> PathBuf {
(**self).file()
}
fn buildable(&self) -> Option<Box<dyn Buildable>> {
(**self).buildable()
}
}
impl Artifact for Box<dyn Artifact> {
fn classifier(&self) -> Option<String> {
(**self).classifier()
}
fn date(&self) -> Option<Date> {
(**self).date()
}
fn extension(&self) -> String {
(**self).extension()
}
fn name(&self) -> String {
(**self).name()
}
fn artifact_type(&self) -> String {
(**self).artifact_type()
}
fn file(&self) -> PathBuf {
(**self).file()
}
fn buildable(&self) -> Option<Box<dyn Buildable>> {
(**self).buildable()
}
}
fn default_file<A: Artifact + ?Sized>(artifact: &A) -> PathBuf {
let as_string = format!(
"{}{}.{}",
artifact.name(),
artifact
.classifier()
.map(|s| format!("-{}", s))
.unwrap_or_default(),
artifact.extension()
);
PathBuf::from(as_string)
}
#[derive(Clone)]
pub struct ConfigurableArtifact {
classifier: Option<String>,
name: String,
extension: String,
artifact_type: Option<String>,
built_by: BuiltByContainer,
file: Option<PathBuf>,
}
impl PartialEq for ConfigurableArtifact {
fn eq(&self, other: &Self) -> bool {
self.classifier == other.classifier
&& self.name == other.name
&& self.extension == other.extension
&& self.artifact_type == other.artifact_type
}
}
impl Eq for ConfigurableArtifact {}
impl Hash for ConfigurableArtifact {
fn hash<H: Hasher>(&self, state: &mut H) {
self.classifier.hash(state);
self.name.hash(state);
self.extension.hash(state);
self.artifact_type.hash(state);
}
}
impl ConfigurableArtifact {
pub fn from_artifact<A: IntoArtifact>(artifact: A) -> Self
where
A::IntoArtifact: 'static,
{
let artifact = artifact.into_artifact();
let container = BuiltByContainer::new();
Self {
classifier: artifact.classifier(),
name: artifact.name(),
extension: artifact.extension(),
artifact_type: Some(artifact.artifact_type()),
built_by: container,
file: Some(artifact.file()),
}
}
pub fn new(name: String, extension: String) -> Self {
Self {
classifier: None,
name,
extension,
artifact_type: None,
built_by: BuiltByContainer::new(),
file: None,
}
}
pub fn set_name(&mut self, name: impl AsRef<str>) {
self.name = name.as_ref().to_string();
}
pub fn set_classifier(&mut self, classifier: impl AsRef<str>) {
self.classifier = Some(classifier.as_ref().to_string());
}
pub fn set_extension(&mut self, extension: impl AsRef<str>) {
self.extension = extension.as_ref().to_string();
}
pub fn set_artifact_type(&mut self, artifact_type: impl AsRef<str>) {
self.artifact_type = Some(artifact_type.as_ref().to_string());
}
pub fn set_file(&mut self, file: PathBuf) {
self.file = Some(file);
}
pub fn built_by<B: IntoBuildable>(&mut self, build: B)
where
B::Buildable: 'static,
{
self.built_by.add(build)
}
}
impl Buildable for ConfigurableArtifact {
fn get_dependencies(&self, project: &Project) -> ProjectResult<HashSet<TaskId>> {
self.built_by.get_dependencies(project)
}
}
impl Debug for ConfigurableArtifact {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.file())
}
}
impl Artifact for ConfigurableArtifact {
fn classifier(&self) -> Option<String> {
self.classifier.clone()
}
fn extension(&self) -> String {
self.extension.clone()
}
fn name(&self) -> String {
self.name.clone()
}
fn artifact_type(&self) -> String {
self.artifact_type.clone().unwrap_or(self.extension())
}
fn file(&self) -> PathBuf {
self.file.clone().unwrap_or_else(|| default_file(self))
}
fn buildable(&self) -> Option<Box<dyn Buildable>> {
Some(Box::new(self.built_by.clone()))
}
}
pub trait IntoArtifact {
type IntoArtifact: Artifact;
fn into_artifact(self) -> Self::IntoArtifact;
}
impl<A: Artifact> IntoArtifact for A {
type IntoArtifact = A;
fn into_artifact(self) -> Self::IntoArtifact {
self
}
}
impl IntoArtifact for PathBuf {
type IntoArtifact = ConfigurableArtifact;
fn into_artifact(self) -> Self::IntoArtifact {
self.as_path().into_artifact()
}
}
impl IntoArtifact for &Path {
type IntoArtifact = ConfigurableArtifact;
fn into_artifact(self) -> Self::IntoArtifact {
let name = self
.file_name()
.expect("no file name found")
.to_str()
.unwrap()
.to_string();
let mut artifact = if name.contains('.') {
let name = name.rsplit_once('.').unwrap().0.to_string();
let ext = self
.extension()
.expect("no extension found")
.to_str()
.unwrap()
.to_string();
ConfigurableArtifact::new(name, ext)
} else {
ConfigurableArtifact {
classifier: None,
name,
extension: "".to_string(),
artifact_type: Some("directory".to_string()),
built_by: Default::default(),
file: None,
}
};
artifact.set_file(self.to_path_buf());
artifact
}
}
impl IntoArtifact for RegularFile {
type IntoArtifact = ConfigurableArtifact;
fn into_artifact(self) -> Self::IntoArtifact {
self.path().into_artifact()
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct ImmutableArtifact {
classifier: Option<String>,
name: String,
extension: String,
artifact_type: String,
file: PathBuf,
}
impl ImmutableArtifact {
pub fn new<A: IntoArtifact>(artifact: A) -> Self {
let as_artifact = artifact.into_artifact();
Self {
classifier: as_artifact.classifier(),
name: as_artifact.name(),
extension: as_artifact.extension(),
artifact_type: as_artifact.artifact_type(),
file: as_artifact.file(),
}
}
}
impl Artifact for ImmutableArtifact {
fn classifier(&self) -> Option<String> {
self.classifier.clone()
}
fn extension(&self) -> String {
self.extension.clone()
}
fn name(&self) -> String {
self.name.clone()
}
fn artifact_type(&self) -> String {
self.artifact_type.clone()
}
fn file(&self) -> PathBuf {
self.file.clone()
}
}
#[cfg(test)]
mod tests {
use crate::flow::shared::{Artifact, IntoArtifact};
use std::path::PathBuf;
#[test]
fn artifact_from_path() {
let path = PathBuf::from("artifact.zip");
let artifact = path.into_artifact();
assert_eq!(artifact.name(), "artifact");
assert_eq!(artifact.extension(), "zip");
}
}