palette_derive 0.4.1

Automatically implement traits from the palette crate.
Documentation
use std::fmt;

use proc_macro2::{Span, TokenStream};
use syn::{GenericParam, Generics, Ident, Path, Turbofish, Type, TypePath};

use meta::KeyValuePair;
use util;

pub fn find_in_generics(
    component: Option<&Type>,
    white_point: Option<&Type>,
    generics: &Generics,
) -> (bool, bool) {
    let mut generic_component = false;
    let mut generic_white_point = false;

    for param in &generics.params {
        if let GenericParam::Type(ref param) = *param {
            if let Some(&Type::Path(TypePath {
                qself: None,
                path:
                    Path {
                        segments: ref component,
                        leading_colon,
                    },
            })) = component
            {
                let first = component.first().map(|s| s.into_value());
                let is_ident_path = leading_colon.is_none()
                    && component.len() == 1
                    && first.unwrap().arguments.is_empty()
                    && first.unwrap().ident == param.ident;

                if is_ident_path {
                    generic_component = true;
                }
            }

            if let Some(&Type::Path(TypePath {
                qself: None,
                path:
                    Path {
                        segments: ref white_point,
                        leading_colon,
                    },
            })) = white_point
            {
                let first = white_point.first().map(|s| s.into_value());
                let is_ident_path = leading_colon.is_none()
                    && white_point.len() == 1
                    && first.unwrap().arguments.is_empty()
                    && first.unwrap().ident == param.ident;

                if is_ident_path {
                    generic_white_point = true;
                }
            }
        }
    }

    (generic_component, generic_white_point)
}

pub fn white_point_type(white_point: Option<Type>, internal: bool) -> Type {
    white_point.unwrap_or_else(|| util::path_type(&["white_point", "D65"], internal))
}

pub fn component_type(component: Option<Type>) -> Type {
    component.unwrap_or_else(|| parse_quote!(f32))
}

pub fn rgb_space_type(rgb_space: Option<Type>, white_point: &Type, internal: bool) -> Type {
    rgb_space.unwrap_or_else(|| {
        let srgb_path = util::path_type(&["encoding", "srgb", "Srgb"], internal);
        parse_quote!((#srgb_path, #white_point))
    })
}

pub fn add_component_where_clause(component: &Type, generics: &mut Generics, internal: bool) {
    util::add_missing_where_clause(generics);

    let where_clause = generics.where_clause.as_mut().unwrap();
    let component_trait_path = util::path(&["Component"], internal);

    where_clause
        .predicates
        .push(parse_quote!(#component: #component_trait_path + _num_traits::Float));
}

pub fn add_white_point_where_clause(white_point: &Type, generics: &mut Generics, internal: bool) {
    util::add_missing_where_clause(generics);

    let where_clause = generics.where_clause.as_mut().unwrap();
    let white_point_trait_path = util::path(&["white_point", "WhitePoint"], internal);

    where_clause
        .predicates
        .push(parse_quote!(#white_point: #white_point_trait_path));
}

pub fn generate_methods(
    ident: &Ident,
    convert_direction: ConvertDirection,
    implementations: &[KeyValuePair],
    component: &Type,
    white_point: &Type,
    rgb_space: &Type,
    turbofish_generics: &Turbofish,
    internal: bool,
) -> Vec<TokenStream> {
    let mut xyz_convert = Some(XyzConvert::Luma);
    let mut methods = vec![];

    for color in implementations {
        if color.key == "Xyz" {
            xyz_convert = None;
        }

        xyz_convert = xyz_convert.map(|current| {
            current.get_best(match &*color.key.to_string() {
                "Rgb" | "Hsl" | "Hsv" | "Hwb" => XyzConvert::Rgb,
                "Lab" => XyzConvert::Lab,
                "Lch" => XyzConvert::Lch,
                "Yxy" => XyzConvert::Yxy,
                "Luma" => XyzConvert::Luma,
                color => panic!("unexpected color type: {}", color),
            })
        });

        let color_name = color.key.to_string();

        let method_name = Ident::new(
            &format!("{}_{}", convert_direction, color_name.to_lowercase()),
            Span::call_site(),
        );
        let color_path = util::color_path(&*color_name, internal);
        let convert_function = color
            .value
            .clone()
            .unwrap_or_else(|| Ident::new(convert_direction.as_ref(), Span::call_site()));

        let method = match &*color_name {
            "Rgb" | "Hsl" | "Hsv" | "Hwb" => {
                let rgb_space_path = util::path(&["rgb", "RgbSpace"], internal);
                quote!(#method_name<_S: #rgb_space_path<WhitePoint = #white_point>>)
            }
            _ => quote!(#method_name),
        };

        let color_ty = match &*color_name {
            "Rgb" => {
                let linear_path = util::path(&["encoding", "Linear"], internal);

                quote!(#color_path<#linear_path<_S>, #component>)
            }
            "Luma" => {
                let linear_path = util::path(&["encoding", "Linear"], internal);

                quote!(#color_path<#linear_path<#white_point>, #component>)
            }
            "Hsl" | "Hsv" | "Hwb" => quote!(#color_path<_S, #component>),
            _ => quote!(#color_path<#white_point, #component>),
        };

        methods.push(match convert_direction {
            ConvertDirection::From => quote! {
                fn #method (color: #color_ty) -> Self {
                    #ident #turbofish_generics::#convert_function(color)
                }
            },
            ConvertDirection::Into => quote! {
                fn #method (self) -> #color_ty {
                    self.#convert_function()
                }
            },
        });
    }

    if let Some(xyz_convert) = xyz_convert {
        let color_path = util::path(&["Xyz"], internal);
        let method_name = Ident::new(&format!("{}_xyz", convert_direction), Span::call_site());
        let into_temporary_name = Ident::new(&format!("into_{}", xyz_convert), Span::call_site());
        let into_color_trait_path = util::path(&["IntoColor"], internal);
        let convert_function = Ident::new(
            &format!("{}_{}", convert_direction, xyz_convert),
            Span::call_site(),
        );

        let method = match convert_direction {
            ConvertDirection::From if xyz_convert == XyzConvert::Rgb => quote! {
                fn #method_name(color: #color_path<#white_point, #component>) -> Self {
                    use #into_color_trait_path;
                    #ident #turbofish_generics::#convert_function(color.#into_temporary_name::<#rgb_space>())
                }
            },
            ConvertDirection::From => quote! {
                fn #method_name(color: #color_path<#white_point, #component>) -> Self {
                    use #into_color_trait_path;
                    #ident #turbofish_generics::#convert_function(color.#into_temporary_name())
                }
            },
            ConvertDirection::Into if xyz_convert == XyzConvert::Rgb => quote! {
                fn #method_name(self) -> #color_path<#white_point, #component> {
                    self.#convert_function::<#rgb_space>().into_xyz()
                }
            },
            ConvertDirection::Into => quote! {
                fn #method_name(self) -> #color_path<#white_point, #component> {
                    self.#convert_function().into_xyz()
                }
            },
        };

        methods.push(method);
    }

    methods
}

pub fn get_convert_color_type(
    color: &str,
    white_point: &Type,
    component: &Type,
    rgb_space: Option<&Type>,
    generics: &mut Generics,
    internal: bool,
) -> Type {
    let color_path = util::color_path(color, internal);

    match color {
        "Rgb" => {
            let rgb_standard_path = util::path(&["rgb", "RgbStandard"], internal);
            let rgb_space_path = util::path(&["rgb", "RgbSpace"], internal);
            generics.params.push(GenericParam::Type(
                Ident::new("_S", Span::call_site()).into(),
            ));

            util::add_missing_where_clause(generics);
            let where_clause = generics.where_clause.as_mut().unwrap();
            if let Some(ref rgb_space) = rgb_space {
                where_clause
                    .predicates
                    .push(parse_quote!(_S: #rgb_standard_path<Space = #rgb_space>));
            } else {
                where_clause
                    .predicates
                    .push(parse_quote!(_S: #rgb_standard_path));
                where_clause
                    .predicates
                    .push(parse_quote!(_S::Space: #rgb_space_path<WhitePoint = #white_point>));
            }

            parse_quote!(#color_path<_S, #component>)
        }
        "Luma" => {
            let luma_standard_path = util::path(&["luma", "LumaStandard"], internal);
            generics.params.push(GenericParam::Type(
                Ident::new("_S", Span::call_site()).into(),
            ));

            util::add_missing_where_clause(generics);
            let where_clause = generics.where_clause.as_mut().unwrap();
            where_clause
                .predicates
                .push(parse_quote!(_S: #luma_standard_path<WhitePoint = #white_point>));
            parse_quote!(#color_path<_S, #component>)
        }
        "Hsl" | "Hsv" | "Hwb" => {
            let rgb_space_path = util::path(&["rgb", "RgbSpace"], internal);

            util::add_missing_where_clause(generics);

            if let Some(ref rgb_space) = rgb_space {
                parse_quote!(#color_path<#rgb_space, #component>)
            } else {
                generics.params.push(GenericParam::Type(
                    Ident::new("_S", Span::call_site()).into(),
                ));

                let where_clause = generics.where_clause.as_mut().unwrap();
                where_clause
                    .predicates
                    .push(parse_quote!(_S: #rgb_space_path<WhitePoint = #white_point>));

                parse_quote!(#color_path<_S, #component>)
            }
        }
        _ => parse_quote!(#color_path<#white_point, #component>),
    }
}

#[derive(Clone, Copy)]
pub enum ConvertDirection {
    From,
    Into,
}

impl fmt::Display for ConvertDirection {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.as_ref().fmt(f)
    }
}

impl AsRef<str> for ConvertDirection {
    fn as_ref(&self) -> &str {
        match *self {
            ConvertDirection::From => "from",
            ConvertDirection::Into => "into",
        }
    }
}

#[derive(PartialEq, Clone, Copy)]
enum XyzConvert {
    Luma,
    Hwb,
    Hsl,
    Hsv,
    Rgb,
    Lab,
    Lch,
    Yxy,
}

impl XyzConvert {
    fn get_best(&self, other: XyzConvert) -> XyzConvert {
        match (*self, other) {
            (XyzConvert::Yxy, _) | (_, XyzConvert::Yxy) => XyzConvert::Yxy,
            (XyzConvert::Lab, _) | (_, XyzConvert::Lab) => XyzConvert::Lab,
            (XyzConvert::Lch, _) | (_, XyzConvert::Lch) => XyzConvert::Lch,
            (XyzConvert::Rgb, _) | (_, XyzConvert::Rgb) => XyzConvert::Rgb,
            (XyzConvert::Hsl, _) | (_, XyzConvert::Hsl) => XyzConvert::Hsl,
            (XyzConvert::Hsv, _) | (_, XyzConvert::Hsv) => XyzConvert::Hsv,
            (XyzConvert::Hwb, _) | (_, XyzConvert::Hwb) => XyzConvert::Hwb,
            (XyzConvert::Luma, XyzConvert::Luma) => XyzConvert::Luma,
        }
    }
}

impl AsRef<str> for XyzConvert {
    fn as_ref(&self) -> &str {
        match *self {
            XyzConvert::Luma => "luma",
            XyzConvert::Hwb => "hwb",
            XyzConvert::Hsl => "hsl",
            XyzConvert::Hsv => "hsv",
            XyzConvert::Rgb => "rgb",
            XyzConvert::Lab => "lab",
            XyzConvert::Lch => "lch",
            XyzConvert::Yxy => "yxy",
        }
    }
}

impl fmt::Display for XyzConvert {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.as_ref().fmt(f)
    }
}