1
//! CSS properties for flowing content into regions (`flow-into`, `flow-from`).
2

            
3
use alloc::string::{String, ToString};
4

            
5
use crate::{corety::AzString, props::formatter::PrintAsCssValue};
6

            
7
// --- flow-into ---
8
#[allow(variant_size_differences)] // repr(C,u8) FFI enum: boxing the large variant would change the C ABI (api.json bindings); size disparity accepted
9
/// CSS `flow-into` property — diverts an element's content into a named flow.
10
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
11
#[repr(C, u8)]
12
#[derive(Default)]
13
pub enum FlowInto {
14
    /// Content is not diverted into any named flow (default).
15
    #[default]
16
    None,
17
    /// Content is diverted into the named flow identified by this string.
18
    Named(AzString),
19
}
20

            
21

            
22
impl PrintAsCssValue for FlowInto {
23
    fn print_as_css_value(&self) -> String {
24
        match self {
25
            Self::None => "none".to_string(),
26
            Self::Named(s) => s.to_string(),
27
        }
28
    }
29
}
30

            
31
// --- flow-from ---
32
#[allow(variant_size_differences)] // repr(C,u8) FFI enum: boxing the large variant would change the C ABI (api.json bindings); size disparity accepted
33
/// CSS `flow-from` property — consumes content from a named flow into a region.
34
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
35
#[repr(C, u8)]
36
#[derive(Default)]
37
pub enum FlowFrom {
38
    /// No named flow is consumed (default).
39
    #[default]
40
    None,
41
    /// Content is consumed from the named flow identified by this string.
42
    Named(AzString),
43
}
44

            
45

            
46
impl PrintAsCssValue for FlowFrom {
47
    fn print_as_css_value(&self) -> String {
48
        match self {
49
            Self::None => "none".to_string(),
50
            Self::Named(s) => s.to_string(),
51
        }
52
    }
53
}
54

            
55
// Formatting to Rust code
56
impl crate::codegen::format::FormatAsRustCode for FlowInto {
57
    fn format_as_rust_code(&self, _tabs: usize) -> String {
58
        match self {
59
            Self::None => String::from("FlowInto::None"),
60
            Self::Named(s) => format!(
61
                "FlowInto::Named(AzString::from_const_str({:?}))",
62
                s.as_str()
63
            ),
64
        }
65
    }
66
}
67

            
68
impl crate::codegen::format::FormatAsRustCode for FlowFrom {
69
    fn format_as_rust_code(&self, _tabs: usize) -> String {
70
        match self {
71
            Self::None => String::from("FlowFrom::None"),
72
            Self::Named(s) => format!(
73
                "FlowFrom::Named(AzString::from_const_str({:?}))",
74
                s.as_str()
75
            ),
76
        }
77
    }
78
}
79

            
80
// --- PARSERS ---
81

            
82
#[cfg(feature = "parser")]
83
pub mod parser {
84
    #[allow(clippy::wildcard_imports)] // parser submodule reuses the parent module's value types
85
    use super::*;
86
    use crate::corety::AzString;
87

            
88
    macro_rules! define_flow_parser {
89
        (
90
            $fn_name:ident,
91
            $struct_name:ident,
92
            $error_name:ident,
93
            $error_owned_name:ident,
94
            $prop_name:expr
95
        ) => {
96
            #[derive(Clone, PartialEq, Eq)]
97
            pub enum $error_name<'a> {
98
                InvalidValue(&'a str),
99
            }
100

            
101
            impl_debug_as_display!($error_name<'a>);
102
            impl_display! { $error_name<'a>, {
103
                InvalidValue(v) => format!("Invalid {} value: \"{}\"", $prop_name, v),
104
            }}
105

            
106
            #[derive(Debug, Clone, PartialEq, Eq)]
107
            #[repr(C, u8)]
108
            pub enum $error_owned_name {
109
                InvalidValue(AzString),
110
            }
111

            
112
            impl $error_name<'_> {
113
                #[must_use] pub fn to_contained(&self) -> $error_owned_name {
114
                    match self {
115
                        Self::InvalidValue(s) => $error_owned_name::InvalidValue(s.to_string().into()),
116
                    }
117
                }
118
            }
119

            
120
            impl $error_owned_name {
121
                #[must_use] pub fn to_shared(&self) -> $error_name<'_> {
122
                    match self {
123
                        Self::InvalidValue(s) => $error_name::InvalidValue(s.as_str()),
124
                    }
125
                }
126
            }
127

            
128
            /// # Errors
129
            ///
130
            /// Returns an error if `input` is not a valid CSS value for this property.
131
6
            pub fn $fn_name(input: &str) -> Result<$struct_name, $error_name<'_>> {
132
6
                let trimmed = input.trim();
133
6
                if trimmed.is_empty() {
134
2
                    return Err($error_name::InvalidValue(input));
135
4
                }
136
4
                match trimmed {
137
4
                    "none" => Ok($struct_name::None),
138
                    // any other value is a custom identifier
139
2
                    ident => Ok($struct_name::Named(ident.to_string().into())),
140
                }
141
6
            }
142
        };
143
    }
144

            
145
    define_flow_parser!(
146
        parse_flow_into,
147
        FlowInto,
148
        FlowIntoParseError,
149
        FlowIntoParseErrorOwned,
150
        "flow-into"
151
    );
152
    define_flow_parser!(
153
        parse_flow_from,
154
        FlowFrom,
155
        FlowFromParseError,
156
        FlowFromParseErrorOwned,
157
        "flow-from"
158
    );
159
}
160

            
161
#[cfg(feature = "parser")]
162
pub use parser::*;
163

            
164
#[cfg(all(test, feature = "parser"))]
165
mod tests {
166
    use super::*;
167

            
168
    #[test]
169
1
    fn test_parse_flow_into() {
170
1
        assert_eq!(parse_flow_into("none").unwrap(), FlowInto::None);
171
1
        assert_eq!(
172
1
            parse_flow_into("my-article-flow").unwrap(),
173
1
            FlowInto::Named("my-article-flow".into())
174
        );
175
1
        assert!(parse_flow_into("").is_err());
176
1
    }
177

            
178
    #[test]
179
1
    fn test_parse_flow_from() {
180
1
        assert_eq!(parse_flow_from("none").unwrap(), FlowFrom::None);
181
1
        assert_eq!(
182
1
            parse_flow_from("  main-thread  ").unwrap(),
183
1
            FlowFrom::Named("main-thread".into())
184
        );
185
1
        assert!(parse_flow_from("").is_err());
186
1
    }
187
}