Skip to content

Commit c9d2646

Browse files
committed
feat: attrs!
1 parent 610ecc7 commit c9d2646

File tree

18 files changed

+473
-259
lines changed

18 files changed

+473
-259
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/frender-attrs/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ either = { version = "1.15.0", optional = true, default-features = false }
99
frender-macro-rules = { version = "0.1.0", path = "../frender-macro-rules", features = [
1010
"define_phantom_wrapper",
1111
] }
12+
frender-const-value = { version = "0.1.0", path = "../frender-const-value" }
13+
frender-const-expr = { version = "0.1.0", path = "../frender-const-expr" }
1214
chtml = { version = "0.1.0", path = "../chtml" }
1315
frender-ssr-html = { version = "0.1.0", path = "../frender-ssr-html", optional = true }
1416
async-str-iter = { version = "0.1.0", path = "../async-str-iter", optional = true }

packages/frender-attrs/src/attributes.rs

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
//! See [`attrs!`](crate::attrs!).
2+
3+
/// Attrs separated by comma.
4+
///
5+
/// The macro input will be parsed as [attrs syntaxes](one) separated by comma.
6+
/// Then all attrs will be [chained](crate::values::Chain).
7+
#[macro_export]
8+
macro_rules! attrs {
9+
($($t:tt)*) => {
10+
$crate::attrs::syntax::paren!(
11+
@{$crate::attrs::syntax}
12+
{($($t)*)}
13+
)
14+
};
15+
}
16+
17+
#[doc(no_inline)]
18+
pub use attrs as comma_separated;
19+
20+
/// An inline expr of [`ConstAttributes<impl HasConstAttributes>`](type@crate::values::r#const::ConstAttributes).
21+
#[doc(hidden)]
22+
#[macro_export]
23+
macro_rules! attrs_const {
24+
($($t:tt)*) => {
25+
$crate::attrs::__private::r#const! {
26+
#[const_impl_mod($crate::attrs::__private::const_impl)]
27+
$($t)*
28+
}
29+
};
30+
}
31+
32+
/// ### Supported attrs syntaxes
33+
///
34+
/// #### literal and `const {..}`
35+
///
36+
/// They will be parsed as a [`const attrs`](const!).
37+
///
38+
/// #### verbatim expr `verbatim!(..)`
39+
///
40+
/// The content will not be parsed. it will be directly used as an expr.
41+
/// It should implement [`IntoAttributes`].
42+
///
43+
/// #### block `{..}`
44+
///
45+
/// The block content will be parsed as [`one attrs`](one).
46+
///
47+
/// #### array `[..]`
48+
///
49+
/// The array will not be parsed. it will be directly used as an expr.
50+
/// The array item should implement [`IntoAttributes`]
51+
/// so that the array implements [`IntoAttributes`].
52+
///
53+
/// #### parenthesis `(..)`
54+
///
55+
/// The content will be parsed with [`attrs`].
56+
///
57+
/// #### if else
58+
///
59+
/// - `if ($predicate:expr) { $s:one_attrs }` will be parsed as `if $predicate { Some(one!({ $s })) } else { None }`
60+
/// - `if ($predicate:expr) { $s:one_attrs } else { $t:one_attrs }` will be parsed as `if $predicate { EitherAttributes::A(one!({ $s })) } else { EitherAttributes::B(one!({ $t })) }`
61+
/// - `if ($predicate:expr) { $one_attrs } else if ($predicate) { $one_attrs } ..` will be parsed recursively.
62+
///
63+
/// Note that the predicate must be wrapped in `( )`.
64+
///
65+
/// #### match
66+
///
67+
/// - `match (never) {}` will be parsed as a value of [`Never`](crate::values::Never)
68+
/// - `match ($expr) { _ => $s:one_attrs }` will be parsed as `match $expr { _ => one!($s) }`
69+
/// - `match ($expr) { _ => $s:one_attrs, _ => $t:one_attrs }` will be parsed as `match $expr { _ => EitherAttributes::A(one!($s)), _ => EitherAttributes::B(one!($t)) }`
70+
///
71+
/// Note that the matched expr must be wrapped in `( )`.
72+
///
73+
/// [`IntoAttributes`]: crate::IntoAttributes
74+
#[doc(hidden)]
75+
#[macro_export]
76+
macro_rules! attrs_one {
77+
($($t:tt)+) => {
78+
$crate::attrs::syntax::one!(
79+
@{$crate::attrs::syntax}
80+
{$($t)+}
81+
)
82+
};
83+
}
84+
85+
#[doc(inline)]
86+
pub use {attrs_const as r#const, attrs_one as one};
87+
88+
pub mod syntax {
89+
pub use frender_const_expr::syntax::*;
90+
91+
pub mod parsed {
92+
pub use crate::values::{Chain, EitherAttributes as Either, Empty, Never};
93+
94+
pub use super::super::r#const;
95+
}
96+
97+
pub mod macros {
98+
#[doc(no_inline)]
99+
pub use frender_const_expr::syntax::macros::verbatim;
100+
101+
#[doc(no_inline)]
102+
pub use attrs;
103+
}
104+
}
105+
106+
#[doc(hidden)]
107+
pub mod __private {
108+
#[doc(hidden)]
109+
pub use frender_const_expr::r#const;
110+
111+
#[doc(hidden)]
112+
pub mod const_impl {
113+
pub use crate::{
114+
impl_HasConstAttributes_for as impl_marker_for,
115+
values::r#const::ConstAttributes as ConstValue,
116+
};
117+
}
118+
}
119+
120+
#[cfg(test)]
121+
mod tests;
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
use std::task::Poll;
2+
3+
use crate::{
4+
csr::{CsrAttributes, RenderAttributes},
5+
ssr::SsrAttributes,
6+
values::{
7+
r#const::{ConstAttributes, HasConstAttributes},
8+
EitherAttributes, Empty,
9+
},
10+
};
11+
12+
use crate::attrs::one;
13+
14+
fn csr_ssr_const<T: ?Sized + HasConstAttributes>(
15+
v: ConstAttributes<T>,
16+
) -> (Vec<(String, String)>, String) {
17+
use async_str_iter::AsyncStrIterator;
18+
19+
let ssr = v.into_ssr_attributes();
20+
let mut ssr = std::pin::pin!(ssr);
21+
let cx = &mut std::task::Context::from_waker(std::task::Waker::noop());
22+
let ssr_str = match ssr.as_mut().poll_next_str(cx) {
23+
Poll::Ready(v) => v.unwrap_or(""),
24+
Poll::Pending => panic!(),
25+
};
26+
27+
let ssr_str = ssr_str.to_string();
28+
29+
match ssr.poll_next_str(cx) {
30+
Poll::Ready(None) => {}
31+
_ => panic!(),
32+
}
33+
34+
let csr = {
35+
struct RenderInit(Vec<(String, String)>);
36+
37+
impl RenderAttributes for RenderInit {
38+
fn set_attribute(&mut self, name: &str, value: &str) {
39+
self.0.push((name.into(), value.into()));
40+
}
41+
42+
fn remove_attribute(&mut self, _: &str) {
43+
panic!()
44+
}
45+
}
46+
47+
let mut renderer = RenderInit(Vec::new());
48+
let mut state = v.render_init(&mut renderer);
49+
50+
struct RenderUpdate;
51+
52+
impl RenderAttributes for RenderUpdate {
53+
fn set_attribute(&mut self, _: &str, _: &str) {
54+
panic!()
55+
}
56+
57+
fn remove_attribute(&mut self, _: &str) {
58+
panic!()
59+
}
60+
}
61+
v.render_update(&mut RenderUpdate, &mut state);
62+
63+
renderer.0
64+
};
65+
66+
(csr, ssr_str)
67+
}
68+
69+
#[test]
70+
fn match_clause() {
71+
let f = || one!(match (panic!()) {});
72+
73+
let _ = f as fn() -> crate::values::Never;
74+
75+
let Empty = one!(match (()) {
76+
_ => (),
77+
});
78+
79+
{
80+
let (csr, ssr) = csr_ssr_const(one!(match (()) {
81+
_ => "",
82+
}));
83+
assert_eq!(csr, []);
84+
assert_eq!(ssr, "");
85+
}
86+
87+
match one!(match (true) {
88+
a if a => "",
89+
_ => attrs!(),
90+
}) {
91+
EitherAttributes::A(ConstAttributes { .. }) => {}
92+
EitherAttributes::B(Empty) => unreachable!(),
93+
}
94+
95+
match one!(match (1) {
96+
// empty (think unit tuple `()` as an empty list)
97+
a if a > 0 => (),
98+
b if b < 0 => {
99+
// empty wrapped in a block
100+
()
101+
}
102+
// empty wrapped in parenthesis
103+
_ => (()),
104+
}) {
105+
EitherAttributes::A(Empty) => {}
106+
EitherAttributes::B(other) => match other {
107+
EitherAttributes::A(Empty) => panic!(),
108+
EitherAttributes::B(Empty) => panic!(),
109+
},
110+
}
111+
}

packages/frender-attrs/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
use chtml as parser;
2-
31
pub mod values;
42

53
#[cfg(feature = "csr")]
64
pub mod csr;
75
#[cfg(feature = "ssr")]
86
pub mod ssr;
97

8+
pub mod attrs;
9+
1010
/// Non reactive attributes with unpinned state.
1111
pub trait IntoAttributes {
1212
type IntoAttributes: Attributes;

packages/frender-attrs/src/values.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,17 @@ impl IntoAttributes for Empty {
1111
}
1212
}
1313

14+
#[derive(Debug, Clone, Copy)]
15+
pub enum Never {}
16+
mod never;
17+
impl IntoAttributes for Never {
18+
type IntoAttributes = never::NeverAttributes;
19+
20+
fn into_attributes(self) -> Self::IntoAttributes {
21+
match self {}
22+
}
23+
}
24+
1425
mod option;
1526
impl<T: IntoAttributes> IntoAttributes for Option<T> {
1627
type IntoAttributes = option::OptionAttributes<T::IntoAttributes>;
@@ -52,5 +63,20 @@ impl<A: IntoAttributes, B: IntoAttributes> IntoAttributes for ::either::Either<A
5263
#[derive(Debug, Clone, Copy)]
5364
pub struct Chain<A, B>(pub A, pub B);
5465
mod chain;
66+
impl<A: IntoAttributes, B: IntoAttributes> IntoAttributes for Chain<A, B> {
67+
type IntoAttributes = chain::ChainAttributes<A::IntoAttributes, B::IntoAttributes>;
68+
69+
fn into_attributes(self) -> Self::IntoAttributes {
70+
let Self(a, b) = self;
71+
chain::ChainAttributes(a.into_attributes(), b.into_attributes())
72+
}
73+
}
5574

5675
pub mod r#const;
76+
impl<M: ?Sized + r#const::HasConstAttributes> IntoAttributes for r#const::ConstAttributes<M> {
77+
type IntoAttributes = Self;
78+
79+
fn into_attributes(self) -> Self::IntoAttributes {
80+
self
81+
}
82+
}

packages/frender-attrs/src/values/chain.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::Attributes;
22

3-
pub struct ChainAttributes<A, B>(A, B);
3+
pub struct ChainAttributes<A, B>(pub(super) A, pub(super) B);
44

55
impl<A: Attributes, B: Attributes> crate::sealed::Attributes for ChainAttributes<A, B> {}
66
impl<A: Attributes, B: Attributes> Attributes for ChainAttributes<A, B> {}

0 commit comments

Comments
 (0)