rstml_control_flow/
lib.rs

1//! Example of controll flow implementations:
2//! 1. One variant is based on tags `<for />` `<if />` `<else />` `<else-if />`
3//! 2. another variand is based on escape character inside unquoted texts `@if
4//!    {}` `@for foo in array {}`
5use std::marker::PhantomData;
6
7use quote::ToTokens;
8use syn::parse::{Parse, ParseStream};
9
10pub mod escape;
11#[cfg(feature = "extendable")]
12pub mod extendable;
13pub mod tags;
14
15#[cfg(feature = "extendable")]
16pub use extendable::ExtendableCustomNode;
17
18// Either variant, with Parse/ToTokens implementation
19#[derive(Copy, Clone, Debug)]
20pub enum Either<A, B> {
21    A(A),
22    B(B),
23}
24impl<A: Parse, B: Parse> Parse for Either<A, B> {
25    fn parse(input: ParseStream) -> syn::Result<Self> {
26        if Self::peek_a(input) {
27            input.parse().map(Self::A)
28        } else {
29            input.parse().map(Self::B)
30        }
31    }
32}
33impl<A: ToTokens, B: ToTokens> ToTokens for Either<A, B> {
34    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
35        match self {
36            Self::A(a) => a.to_tokens(tokens),
37            Self::B(b) => b.to_tokens(tokens),
38        }
39    }
40}
41
42#[allow(dead_code)]
43impl<A, B> Either<A, B> {
44    pub fn peek_a(stream: ParseStream) -> bool
45    where
46        A: Parse,
47        B: Parse,
48    {
49        stream.fork().parse::<A>().is_ok()
50    }
51    pub fn to_b(self) -> Option<B> {
52        match self {
53            Self::A(_) => None,
54            Self::B(b) => Some(b),
55        }
56    }
57    pub fn to_a(self) -> Option<A> {
58        match self {
59            Self::A(a) => Some(a),
60            Self::B(_) => None,
61        }
62    }
63    pub fn is_b(self) -> bool {
64        match self {
65            Self::A(_) => false,
66            Self::B(_) => true,
67        }
68    }
69    pub fn is_a(self) -> bool {
70        match self {
71            Self::A(_) => true,
72            Self::B(_) => false,
73        }
74    }
75}
76
77pub struct EitherA<A, B>(pub A, pub PhantomData<B>);
78pub struct EitherB<A, B>(pub PhantomData<A>, pub B);
79
80impl<A, B> TryFrom<Either<A, B>> for EitherA<A, B> {
81    type Error = Either<A, B>;
82    fn try_from(value: Either<A, B>) -> Result<Self, Self::Error> {
83        match value {
84            Either::A(a) => Ok(EitherA(a, PhantomData)),
85            rest => Err(rest),
86        }
87    }
88}
89
90impl<A, B> TryFrom<Either<A, B>> for EitherB<A, B> {
91    type Error = Either<A, B>;
92    fn try_from(value: Either<A, B>) -> Result<Self, Self::Error> {
93        match value {
94            Either::B(b) => Ok(EitherB(PhantomData, b)),
95            rest => Err(rest),
96        }
97    }
98}
99
100impl<A, B> From<EitherA<A, B>> for Either<A, B> {
101    fn from(value: EitherA<A, B>) -> Self {
102        Self::A(value.0)
103    }
104}
105
106impl<A, B> From<EitherB<A, B>> for Either<A, B> {
107    fn from(value: EitherB<A, B>) -> Self {
108        Self::B(value.1)
109    }
110}
111
112pub trait TryIntoOrCloneRef<T>: Sized {
113    fn try_into_or_clone_ref(self) -> Either<T, Self>;
114    fn new_from_value(value: T) -> Self;
115}
116
117impl<T> TryIntoOrCloneRef<T> for T {
118    fn try_into_or_clone_ref(self) -> Either<T, Self> {
119        Either::A(self)
120    }
121    fn new_from_value(value: T) -> Self {
122        value
123    }
124}
125
126#[cfg(test)]
127mod tests {
128
129    #[test]
130    #[cfg(feature = "extendable")]
131    fn test_mixed_tags_and_escape() {
132        use quote::ToTokens;
133        use rstml::node::Node;
134
135        use crate::{escape, tags, ExtendableCustomNode};
136
137        let tokens = quote::quote! {
138                @if true {
139                    <p>True</p>
140                    <for foo in array !>
141                        <p>Foo</p>
142                    </for>
143                }
144                else {
145                    <p>False</p>
146                }
147                @for foo in array {
148                    <if foo == 1 !>
149                        <p>Foo</p>
150                    </if>
151                    <p>Foo</p>
152                }
153        };
154
155        let result = ExtendableCustomNode::parse2_with_config::<(
156            tags::Conditions,
157            escape::EscapeCode,
158        )>(Default::default(), tokens);
159        let ok = result.into_result().unwrap();
160        assert_eq!(ok.len(), 2);
161
162        let Node::Custom(c) = &ok[0] else {
163            unreachable!()
164        };
165        let escape_if = c.try_downcast_ref::<escape::EscapeCode>().unwrap();
166        let escape::EscapedExpr::If(if_) = &escape_if.expression else {
167            unreachable!()
168        };
169        assert_eq!(if_.condition.to_token_stream().to_string(), "true");
170        let for_tag = &if_.then_branch.body[1];
171        let Node::Custom(c) = &for_tag else {
172            unreachable!()
173        };
174        let for_tag = c.try_downcast_ref::<tags::Conditions>().unwrap();
175        let tags::Conditions::For(for_) = for_tag else {
176            unreachable!()
177        };
178
179        assert_eq!(for_.pat.to_token_stream().to_string(), "foo");
180
181        let Node::Custom(c) = &ok[1] else {
182            unreachable!()
183        };
184
185        let escape_for = c.try_downcast_ref::<escape::EscapeCode>().unwrap();
186        let escape::EscapedExpr::For(for_) = &escape_for.expression else {
187            unreachable!()
188        };
189        assert_eq!(for_.pat.to_token_stream().to_string(), "foo");
190    }
191}