haddock 0.1.3

Docker Compose for Podman
use std::{
    env,
    error::Error,
    fmt::{self, Formatter},
    hash::Hash,
    marker::PhantomData,
    str::FromStr,
};

use anyhow::{anyhow, Result};
use console::{style, StyledObject};
use indexmap::IndexSet;
use once_cell::sync::Lazy;
use serde::{
    de::{self, SeqAccess, Visitor},
    Deserializer, Serialize, Serializer,
};
use serde_with::{
    de::DeserializeAsWrap, formats::Separator, ser::SerializeAsWrap, DeserializeAs, SerializeAs,
};
use sha2::{Digest as _, Sha256};

pub(crate) static STYLED_WARNING: Lazy<StyledObject<&str>> =
    Lazy::new(|| style("Warning:").yellow().bold());

pub(crate) fn parse_container_path<T, U>(s: &str) -> Result<(Option<T>, U)>
where
    T: FromStr,
    T::Err: Error + Send + Sync + 'static,
    U: FromStr,
    U::Err: Error + Send + Sync + 'static,
{
    if let Some(pos) = s.find(':') {
        Ok((Some(s[..pos].parse()?), s[pos + 1..].parse()?))
    } else {
        Ok((None, s.parse()?))
    }
}

pub(crate) fn parse_key_val<T, U>(s: &str) -> Result<(T, U)>
where
    T: FromStr,
    T::Err: Error + Send + Sync + 'static,
    U: FromStr,
    U::Err: Error + Send + Sync + 'static,
{
    let pos = s.find('=').ok_or_else(|| {
        anyhow!(
            "no '{}' found in '{}'",
            style("=").yellow(),
            style(s).yellow()
        )
    })?;

    Ok((s[..pos].parse()?, s[pos + 1..].parse()?))
}

pub(crate) fn parse_key_val_opt<T, U>(s: &str) -> Result<(T, Option<U>)>
where
    T: FromStr,
    T::Err: Error + Send + Sync + 'static,
    U: FromStr,
    U::Err: Error + Send + Sync + 'static,
{
    if let Some(pos) = s.find('=') {
        Ok((s[..pos].parse()?, Some(s[pos + 1..].parse()?)))
    } else {
        Ok((s.parse()?, None))
    }
}

macro_rules! regex {
    ($re:literal $(,)?) => {{
        static RE: once_cell::sync::OnceCell<regex::Regex> = once_cell::sync::OnceCell::new();
        RE.get_or_init(|| regex::Regex::new($re).unwrap())
    }};
}

pub(crate) use regex;

pub(crate) trait Digest {
    fn digest(&self) -> String;
}

impl<T> Digest for T
where
    T: Serialize,
{
    fn digest(&self) -> String {
        format!(
            "{:x}",
            Sha256::digest(serde_yaml::to_string(self).unwrap().as_bytes())
        )
    }
}

pub(crate) struct DisplayFromAny;

impl<'de, T> DeserializeAs<'de, T> for DisplayFromAny
where
    T: From<String>,
{
    fn deserialize_as<D>(deserializer: D) -> Result<T, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct AnyVisitor<T>(PhantomData<T>);

        impl<'de, T> Visitor<'de> for AnyVisitor<T>
        where
            T: From<String>,
        {
            type Value = T;

            fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
                formatter.write_str("a displayable type")
            }

            fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
            where
                E: de::Error,
            {
                Ok(T::from(v.to_string()))
            }

            fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
            where
                E: de::Error,
            {
                Ok(T::from(v.to_string()))
            }

            fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
            where
                E: de::Error,
            {
                Ok(T::from(v.to_string()))
            }

            fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
            where
                E: de::Error,
            {
                Ok(T::from(v.to_string()))
            }

            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
            where
                E: de::Error,
            {
                Ok(T::from(v.to_string()))
            }
        }

        deserializer.deserialize_any(AnyVisitor(PhantomData))
    }
}

impl<T> SerializeAs<T> for DisplayFromAny
where
    T: Serialize,
{
    fn serialize_as<S>(source: &T, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        source.serialize(serializer)
    }
}

pub(crate) struct DuplicateInsertsLastWinsSet<T>(PhantomData<T>);

impl<'de, T, U> DeserializeAs<'de, IndexSet<T>> for DuplicateInsertsLastWinsSet<U>
where
    T: Eq + Hash,
    U: DeserializeAs<'de, T>,
{
    fn deserialize_as<D>(deserializer: D) -> Result<IndexSet<T>, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct SeqVisitor<T, U>(PhantomData<(T, U)>);

        impl<'de, T, U> Visitor<'de> for SeqVisitor<T, U>
        where
            T: Eq + Hash,
            U: DeserializeAs<'de, T>,
        {
            type Value = IndexSet<T>;

            fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
                formatter.write_str("a sequence")
            }

            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
            where
                A: SeqAccess<'de>,
            {
                let mut values = Self::Value::with_capacity(seq.size_hint().unwrap_or(0).min(4096));

                while let Some(value) = seq
                    .next_element()?
                    .map(DeserializeAsWrap::<T, U>::into_inner)
                {
                    values.replace(value);
                }

                Ok(values)
            }
        }

        deserializer.deserialize_seq(SeqVisitor::<T, U>(PhantomData))
    }
}

impl<T, U> SerializeAs<IndexSet<T>> for DuplicateInsertsLastWinsSet<U>
where
    T: Eq + Hash,
    U: SerializeAs<T>,
{
    fn serialize_as<S>(source: &IndexSet<T>, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.collect_seq(source.iter().map(|item| SerializeAsWrap::<T, U>::new(item)))
    }
}

pub(crate) struct PathSeparator;

impl Separator for PathSeparator {
    fn separator() -> &'static str {
        Box::leak(
            env::var("COMPOSE_PATH_SEPARATOR")
                .unwrap_or_else(|_| {
                    String::from(if cfg!(unix) {
                        ":"
                    } else if cfg!(windows) {
                        ";"
                    } else {
                        unreachable!()
                    })
                })
                .into_boxed_str(),
        )
    }
}