omena-scss-eval 0.3.0

Oracle-first SCSS and Less value evaluator rail for Omena CSS
Documentation
use omena_value_lattice::{
    format_css_number, parse_reducible_ceil_value, parse_reducible_floor_value,
    substitute_static_css_function_references_in_value_until_stable,
};

use crate::value_eval::reduce_static_less_numeric_value;

use super::{
    less_colors::{
        parse_static_less_alpha_value, parse_static_less_argb_value,
        parse_static_less_average_value, parse_static_less_blue_value,
        parse_static_less_color_value, parse_static_less_contrast_value,
        parse_static_less_darken_value, parse_static_less_desaturate_value,
        parse_static_less_difference_value, parse_static_less_exclusion_value,
        parse_static_less_fade_value, parse_static_less_fadein_value,
        parse_static_less_fadeout_value, parse_static_less_green_value,
        parse_static_less_greyscale_value, parse_static_less_hardlight_value,
        parse_static_less_hsv_value, parse_static_less_hsva_value, parse_static_less_hsvhue_value,
        parse_static_less_hsvsaturation_value, parse_static_less_hsvvalue_value,
        parse_static_less_hue_value, parse_static_less_lighten_value,
        parse_static_less_lightness_value, parse_static_less_luma_value,
        parse_static_less_luminance_value, parse_static_less_mix_value,
        parse_static_less_multiply_value, parse_static_less_negation_value,
        parse_static_less_overlay_value, parse_static_less_red_value,
        parse_static_less_rgb_color_value, parse_static_less_saturate_value,
        parse_static_less_saturation_value, parse_static_less_screen_value,
        parse_static_less_shade_value, parse_static_less_softlight_value,
        parse_static_less_spin_value, parse_static_less_tint_value,
    },
    less_numbers::{
        parse_static_less_acos_value, parse_static_less_asin_value, parse_static_less_atan_value,
        parse_static_less_convert_value, parse_static_less_cos_value,
        parse_static_less_extract_value, parse_static_less_get_unit_value,
        parse_static_less_length_value, parse_static_less_percentage_value,
        parse_static_less_pi_value, parse_static_less_range_value, parse_static_less_round_value,
        parse_static_less_sin_value, parse_static_less_tan_value, parse_static_less_unit_value,
    },
    less_predicates::{
        parse_static_less_boolean_value, parse_static_less_if_value,
        parse_static_less_iscolor_value, parse_static_less_isdefined_value,
        parse_static_less_isem_value, parse_static_less_iskeyword_value,
        parse_static_less_isnumber_value, parse_static_less_ispercentage_value,
        parse_static_less_ispixel_value, parse_static_less_isruleset_value,
        parse_static_less_isstring_value, parse_static_less_isunit_value,
        parse_static_less_isurl_value,
    },
    less_strings::{
        parse_static_less_escape_value, parse_static_less_format_value,
        parse_static_less_replace_value, parse_static_less_url_escape_value,
        reduce_static_less_escaped_string_value,
    },
    model::StaticLessResolvedValue,
};

pub(super) fn reduce_static_less_value(value: String) -> String {
    reduce_static_less_value_with_escape_flag(value).text
}

pub(super) fn reduce_static_less_value_with_escape_flag(value: String) -> StaticLessResolvedValue {
    if let Some(escaped) = parse_static_less_escape_value(value.as_str())
        .or_else(|| reduce_static_less_escaped_string_value(value.as_str()))
    {
        return StaticLessResolvedValue {
            text: escaped,
            escaped: true,
        };
    }
    if let Some(encoded) = parse_static_less_url_escape_value(value.as_str()) {
        return StaticLessResolvedValue {
            text: encoded,
            escaped: false,
        };
    }
    let value = reduce_static_less_numeric_value(value);
    let text = substitute_static_css_function_references_in_value_until_stable(
        value.as_str(),
        &[
            ("unit", parse_static_less_unit_value),
            ("get-unit", parse_static_less_get_unit_value),
            ("convert", parse_static_less_convert_value),
            ("if", parse_static_less_if_value),
            ("boolean", parse_static_less_boolean_value),
            ("percentage", parse_static_less_percentage_value),
            ("red", parse_static_less_red_value),
            ("green", parse_static_less_green_value),
            ("blue", parse_static_less_blue_value),
            ("alpha", parse_static_less_alpha_value),
            ("hue", parse_static_less_hue_value),
            ("saturation", parse_static_less_saturation_value),
            ("lightness", parse_static_less_lightness_value),
            ("hsv", parse_static_less_hsv_value),
            ("hsva", parse_static_less_hsva_value),
            ("hsvhue", parse_static_less_hsvhue_value),
            ("hsvsaturation", parse_static_less_hsvsaturation_value),
            ("hsvvalue", parse_static_less_hsvvalue_value),
            ("luma", parse_static_less_luma_value),
            ("luminance", parse_static_less_luminance_value),
            ("contrast", parse_static_less_contrast_value),
            ("color", parse_static_less_color_value),
            ("argb", parse_static_less_argb_value),
            ("fade", parse_static_less_fade_value),
            ("fadein", parse_static_less_fadein_value),
            ("fadeout", parse_static_less_fadeout_value),
            ("mix", parse_static_less_mix_value),
            ("tint", parse_static_less_tint_value),
            ("shade", parse_static_less_shade_value),
            ("multiply", parse_static_less_multiply_value),
            ("screen", parse_static_less_screen_value),
            ("overlay", parse_static_less_overlay_value),
            ("softlight", parse_static_less_softlight_value),
            ("hardlight", parse_static_less_hardlight_value),
            ("difference", parse_static_less_difference_value),
            ("exclusion", parse_static_less_exclusion_value),
            ("average", parse_static_less_average_value),
            ("negation", parse_static_less_negation_value),
            ("lighten", parse_static_less_lighten_value),
            ("darken", parse_static_less_darken_value),
            ("saturate", parse_static_less_saturate_value),
            ("desaturate", parse_static_less_desaturate_value),
            ("spin", parse_static_less_spin_value),
            ("greyscale", parse_static_less_greyscale_value),
            ("ceil", parse_reducible_ceil_value),
            ("floor", parse_reducible_floor_value),
            ("round", parse_static_less_round_value),
            ("pi", parse_static_less_pi_value),
            ("sin", parse_static_less_sin_value),
            ("cos", parse_static_less_cos_value),
            ("tan", parse_static_less_tan_value),
            ("asin", parse_static_less_asin_value),
            ("acos", parse_static_less_acos_value),
            ("atan", parse_static_less_atan_value),
            ("isnumber", parse_static_less_isnumber_value),
            ("iscolor", parse_static_less_iscolor_value),
            ("isstring", parse_static_less_isstring_value),
            ("iskeyword", parse_static_less_iskeyword_value),
            ("isurl", parse_static_less_isurl_value),
            ("isdefined", parse_static_less_isdefined_value),
            ("isruleset", parse_static_less_isruleset_value),
            ("ispixel", parse_static_less_ispixel_value),
            ("ispercentage", parse_static_less_ispercentage_value),
            ("isem", parse_static_less_isem_value),
            ("isunit", parse_static_less_isunit_value),
            ("length", parse_static_less_length_value),
            ("extract", parse_static_less_extract_value),
            ("range", parse_static_less_range_value),
            ("replace", parse_static_less_replace_value),
            ("%", parse_static_less_format_value),
        ],
    )
    .unwrap_or(value);
    let text = parse_static_less_rgb_color_value(text.as_str()).unwrap_or(text);
    StaticLessResolvedValue {
        text,
        escaped: false,
    }
}

pub(super) fn format_static_less_math_number(value: f64) -> Option<String> {
    value
        .is_finite()
        .then(|| format_static_less_channel_number(if value.abs() < 1e-10 { 0.0 } else { value }))
}

pub(super) fn format_static_less_number(value: f64) -> String {
    let formatted = format_css_number(value);
    if let Some(suffix) = formatted.strip_prefix('.') {
        return format!("0.{suffix}");
    }
    if let Some(suffix) = formatted.strip_prefix("-.") {
        return format!("-0.{suffix}");
    }
    formatted
}

pub(super) fn format_static_less_channel_number(value: f64) -> String {
    let formatted = if value.fract() == 0.0 {
        format!("{value:.0}")
    } else {
        format!("{value:.8}")
            .trim_end_matches('0')
            .trim_end_matches('.')
            .to_string()
    };
    if let Some(suffix) = formatted.strip_prefix('.') {
        return format!("0.{suffix}");
    }
    if let Some(suffix) = formatted.strip_prefix("-.") {
        return format!("-0.{suffix}");
    }
    formatted
}