rstml_control_flow/
lib.rs1use 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#[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}