use syn::{Expr, spanned::Spanned as _};
use crate::{
print::Printer,
unparse::{unparse_local, unparse_pat},
vendor::ast::{ControlFlow, ControlFlowKind, Element, IfExpr, IfOrBlock, Markup},
};
impl<'a, 'b> Printer<'a, 'b> {
pub fn print_control_flow<E: Into<Element>>(
&mut self,
control_flow: ControlFlow<E>,
indent_level: usize,
) {
self.print_inline_comment_and_whitespace(
control_flow.at_token.span.span().start(),
indent_level,
true,
);
match control_flow.kind {
ControlFlowKind::If(if_expr) => {
self.write("@");
self.print_if_expr(if_expr, indent_level);
}
ControlFlowKind::For(for_expr) => {
self.write("@for ");
self.write(&unparse_pat(&for_expr.pat, self.base_indent + indent_level).join("\n"));
self.write(" in ");
match for_expr.expr {
Expr::Range(range_expr) => {
self.print_range(range_expr, indent_level);
}
_ => {
self.print_expr(for_expr.expr, indent_level);
}
}
self.write(" ");
self.print_block(for_expr.body, indent_level);
}
ControlFlowKind::Let(local) => {
let let_indent_level = match indent_level {
0 => 0,
indent_level => indent_level - 1,
};
let unparsed_lines = unparse_local(&local, let_indent_level);
self.write("@");
match unparsed_lines.len() {
0 => {}
1 => {
self.write(&unparsed_lines[0]);
}
_ => {
self.write(unparsed_lines[0].trim_start());
for line in &unparsed_lines[1..] {
self.new_line(0);
self.write(line);
}
}
}
self.write(";");
self.print_attr_comment(local.semi_token.span().end());
}
ControlFlowKind::Match(match_expr) => {
self.write("@match ");
self.print_expr(match_expr.expr, indent_level);
self.write(" {");
self.print_attr_comment(match_expr.brace_token.span.open().span().end());
for arm in match_expr.arms {
self.new_line(indent_level + 1);
self.write(&unparse_pat(&arm.pat, self.base_indent + indent_level).join("\n"));
if let Some((_, guard_cond)) = arm.guard {
self.write(" if ");
self.print_expr(guard_cond, indent_level);
}
self.write(" => ");
match arm.body {
Markup::Block(_) => {
self.print_markup(arm.body, indent_level + 1, true);
}
Markup::Lit(_) | Markup::Element(_) | Markup::Splice { .. } => {
self.print_markup(arm.body, indent_level + 1, true);
self.write(",");
}
_ => unimplemented!(),
}
}
self.print_trailing_comments(match_expr.brace_token.span, indent_level + 1);
self.new_line(indent_level);
self.write("}");
self.print_attr_comment(match_expr.brace_token.span.close().span().end());
}
ControlFlowKind::While(while_expr) => {
self.write("@while ");
match while_expr.cond {
Expr::Let(expr_let) => {
self.write("let ");
self.write(
&unparse_pat(&expr_let.pat, self.base_indent + indent_level).join("\n"),
);
self.write(" = ");
self.print_expr(*expr_let.expr, indent_level);
self.write(" ");
}
_ => {
self.print_expr(while_expr.cond, indent_level);
self.write(" ");
}
}
self.print_block(while_expr.body, indent_level);
}
}
}
fn print_if_expr<E: Into<Element>>(&mut self, if_expr: IfExpr<E>, indent_level: usize) {
self.write("if ");
match if_expr.cond {
Expr::Let(expr_let) => {
self.write("let ");
self.write(&unparse_pat(&expr_let.pat, self.base_indent + indent_level).join("\n"));
self.write(" = ");
self.print_expr(*expr_let.expr, indent_level);
self.write(" ");
}
_ => {
self.print_expr(if_expr.cond, indent_level);
self.write(" ");
}
}
self.print_block(if_expr.then_branch, indent_level);
if let Some((_, _, if_or_block)) = if_expr.else_branch {
self.write(" @else ");
match *if_or_block {
IfOrBlock::If(else_if_expr) => {
self.print_if_expr(else_if_expr, indent_level);
}
IfOrBlock::Block(block) => {
self.print_block(block, indent_level);
}
}
}
}
fn print_range(&mut self, range_expr: syn::ExprRange, indent_level: usize) {
if let Some(ref start) = range_expr.start {
self.print_expr(*start.clone(), indent_level);
}
match range_expr.limits {
syn::RangeLimits::HalfOpen(_) => self.write(".."),
syn::RangeLimits::Closed(_) => self.write("..="),
}
if let Some(ref end) = range_expr.end {
self.print_expr(*end.clone(), indent_level);
}
}
}
#[cfg(test)]
mod test {
use crate::testing::*;
test_default!(
control_if,
r#"
html! { @if user == Princess::Luna {h1{"Super secret woona to-do list"}
ul{li{"Nuke the Crystal Empire"}li{"Kick a puppy"}li{"Evil laugh"}}}}
"#,
r#"
html! {
@if user == Princess::Luna {
h1 { "Super secret woona to-do list" }
ul {
li { "Nuke the Crystal Empire" }
li { "Kick a puppy" }
li { "Evil laugh" }
}
}
}
"#
);
test_default!(
control_if_else,
r#"
html! { @if user == Princess::Luna {h1{"Super secret woona to-do list"}
ul{li{"Nuke the Crystal Empire"}li{"Kick a puppy"}li{"Evil laugh"}}}
@else { p { "Nothing to see here; move along." } }}
"#,
r#"
html! {
@if user == Princess::Luna {
h1 { "Super secret woona to-do list" }
ul {
li { "Nuke the Crystal Empire" }
li { "Kick a puppy" }
li { "Evil laugh" }
}
} @else {
p { "Nothing to see here; move along." }
}
}
"#
);
test_default!(
control_if_elseif_else,
r#"
html! { @if user == Princess::Luna {h1{"Super secret woona to-do list"}
ul{li{"Nuke the Crystal Empire"}li{"Kick a puppy"}li{"Evil laugh"}}}
@else if user==Princess::Celestia{p{"Sister, please stop reading my diary. It's rude."}}
@else { p { "Nothing to see here; move along." } }}
"#,
r#"
html! {
@if user == Princess::Luna {
h1 { "Super secret woona to-do list" }
ul {
li { "Nuke the Crystal Empire" }
li { "Kick a puppy" }
li { "Evil laugh" }
}
} @else if user == Princess::Celestia {
p { "Sister, please stop reading my diary. It's rude." }
} @else {
p { "Nothing to see here; move along." }
}
}
"#
);
test_default!(
if_let,
r#"
html! { p { "Hello, " @if let Some(name) = user { (name) } @else { "stranger" } "!"}}
"#,
r#"
html! {
p {
"Hello, "
@if let Some(name) = user { (name) } @else { "stranger" }
"!"
}
}
"#
);
test_small_line!(
if_let_long,
r#"
html! { p { "Hello, " @if let Some(name) =
usere.eeeeeeeeeeeeeeeeeeeeeeeeeeeeeee.eeeeeeeeeeeeeeeee.eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
{ (name) } @else { "stranger" } "!"}}
"#,
r#"
html! {
p {
"Hello, "
@if let Some(name) = {
usere
.eeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
.eeeeeeeeeeeeeeeee
.eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
} { (name) } @else {
"stranger"
}
"!"
}
}
"#
);
test_default!(
control_if_or_chain,
r#"
html! { @if user == 0 || user == 1 {
"Super secret woona to-do list"
} }
"#,
r#"
html! {
@if user == 0 || user == 1 { "Super secret woona to-do list" }
}
"#
);
test_default!(
control_if_or_chain_2,
r#"
html! { @if user == Princess::Luna || user == Princess::Lina {
h1 { "Super secret woona to-do list" }
ul { li { "Nuke the Crystal Empire" } li { "Kick a puppy" } li { "Evil laugh" } } } }
"#,
r#"
html! {
@if user == Princess::Luna || user == Princess::Lina {
h1 { "Super secret woona to-do list" }
ul {
li { "Nuke the Crystal Empire" }
li { "Kick a puppy" }
li { "Evil laugh" }
}
}
}
"#
);
test_default!(
if_let_and,
r#"
html! { @if let Some(name) = user && user == "Bob" { (name) }
@else { "stranger" }}
"#,
r#"
html! {
@if let Some(name) = user && user == "Bob" { (name) } @else { "stranger" }
}
"#
);
test_default!(
if_let_and_chained,
r#"
html! { @if let Some(user) = session && let Some(role) = user.role && role == "admin" {
(user.name) } @else { "stranger" } }
"#,
r#"
html! {
@if let Some(user) = session && let Some(role) = user.role && role == "admin" {
(user.name)
} @else { "stranger" }
}
"#
);
test_default!(
control_for,
r#"
html!{p{"My favorite ponies are:"}ol{@for name in &names{li{(name)}}}}
"#,
r#"
html! {
p { "My favorite ponies are:" }
ol {
@for name in &names {
li { (name) }
}
}
}
"#
);
test_default!(
control_let,
r#"
html!{@for name in &names{@let first_letter=name.chars().next().unwrap();
p{"The first letter of " b{(name)}" is " b{(first_letter)}"."}}}
"#,
r#"
html! {
@for name in &names {
@let first_letter = name.chars().next().unwrap();
p {
"The first letter of "
b { (name) }
" is "
b { (first_letter) }
"."
}
}
}
"#
);
test_default!(
control_match,
r#"
html! { @match user { Princess::Luna => { h1 { "Super secret woona to-do list" } ul { li {
"Nuke the Crystal Empire" } li { "Kick a puppy" } li { "Evil laugh" } } },
Princess::Celestia => { p { "Sister, please stop reading my diary. It's rude." } }, _ => p
{ "Nothing to see here; move along." } } }
"#,
r#"
html! {
@match user {
Princess::Luna => {
h1 { "Super secret woona to-do list" }
ul {
li { "Nuke the Crystal Empire" }
li { "Kick a puppy" }
li { "Evil laugh" }
}
}
Princess::Celestia => {
p { "Sister, please stop reading my diary. It's rude." }
}
_ => p { "Nothing to see here; move along." },
}
}
"#
);
test_default!(
control_match_all_cases,
r#"
html! { @match user {
0 => { h1 { "Super secret woona to-do list" } ul { li { "Nuke the Crystal Empire" } li { "Kick a puppy" } li { "Evil laugh" } } }
1 => p { "Sister, please stop reading my diary. It's rude." }, 2 => (user),
3 => ( super_long_splice .with_a_super_long_method() .and_an_other_super_super_long_method_to_call_afer() .unwarp()),
_ => "lit", } }
"#,
r#"
html! {
@match user {
0 => {
h1 { "Super secret woona to-do list" }
ul {
li { "Nuke the Crystal Empire" }
li { "Kick a puppy" }
li { "Evil laugh" }
}
}
1 => p { "Sister, please stop reading my diary. It's rude." },
2 => (user),
3 => ({
super_long_splice
.with_a_super_long_method()
.and_an_other_super_super_long_method_to_call_afer()
.unwarp()
}),
_ => "lit",
}
}
"#
);
test_default!(
control_match_with_guard,
r#"
html!{@match user{Princess::Luna if !is_asleep=>{h1{"Title"}
h2{"Subtitle"}} _=>p{"Nothing to see here; move along."}}}
"#,
r#"
html! {
@match user {
Princess::Luna if !is_asleep => {
h1 { "Title" }
h2 { "Subtitle" }
}
_ => p { "Nothing to see here; move along." },
}
}
"#
);
test_default!(
control_match_with_multiple_match_patterns,
r#"
html!{@match aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
{ 0 | 1 | 2 => "yes", _ => "no" }}
"#,
r#"
html! {
@match aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa {
0 | 1 | 2 => "yes",
_ => "no",
}
}
"#
);
test_default!(
control_match_with_multiple_match_patterns_2,
r#"
html!{@match user{Princess::Luna|Princess::Lina=>{h1{"Title"}
h2{"Subtitle"}} _=>p{"Nothing to see here; move along."},}}
"#,
r#"
html! {
@match user {
Princess::Luna | Princess::Lina => {
h1 { "Title" }
h2 { "Subtitle" }
}
_ => p { "Nothing to see here; move along." },
}
}
"#
);
test_default!(
control_while,
r#"
html! { @while flag {p{"flag is true"}}}
"#,
r#"
html! {
@while flag {
p { "flag is true" }
}
}
"#
);
test_default!(
control_while_let,
r#"
html! { @while let Some(value) = iter {p{(value)}}}
"#,
r#"
html! {
@while let Some(value) = iter {
p { (value) }
}
}
"#
);
test_default!(
comment_inline,
r##"
use maud::DOCTYPE;
html!{
// <!DOCTYPE html>
(DOCTYPE)
}
"##,
r##"
use maud::DOCTYPE;
html! {
// <!DOCTYPE html>
(DOCTYPE)
}
"##
);
test_default!(
control_let_long_field_access,
r#"
html! { button { @let asdsfdfsdgfjksdnglksdjgdsgx = ysdasdadsadasdsadsadsadafdfsdgfsdgdssfsdflsjfisfjsfgnsjklfnakjsfnasjkdfnsasfsdfdsfsdfsfsdfsdgdgdlgkjdsfklajdnklasdnfklsd.nflksdngflksdgnsddfj; } }
"#,
r#"
html! {
button {
@let asdsfdfsdgfjksdnglksdjgdsgx = ysdasdadsadasdsadsadsadafdfsdgfsdgdssfsdflsjfisfjsfgnsjklfnakjsfnasjkdfnsasfsdfdsfsdfsfsdfsdgdgdlgkjdsfklajdnklasdnfklsd
.nflksdngflksdgnsddfj;
}
}
"#
);
test_default!(
control_let_long_binary_expr,
r#"
html! { button { @let asdsfdfsdgfjksdnglksdjgdsgx = ysdasdadsadasdsadsadsadafdfsdgfsdgdssfsdflsjfisfjsfgnsjklfnakjsfnasjkdfnsasfsdfdsfsdfsfsdfsdgdgdlgkjdsfklajdnklasdnfklsdnflksdngflksdgnsddfj + asdas; } }
"#,
r#"
html! {
button {
@let asdsfdfsdgfjksdnglksdjgdsgx = ysdasdadsadasdsadsadsadafdfsdgfsdgdssfsdflsjfisfjsfgnsjklfnakjsfnasjkdfnsasfsdfdsfsdfsfsdfsdgdgdlgkjdsfklajdnklasdnfklsdnflksdngflksdgnsddfj
+ asdas;
}
}
"#
);
test_default!(
control_let_medium_length_wraps,
r#"
html! { button { @let this_variable_name_is_long_enough_to_cause_wrapping = some_object.method().call().chain(); } }
"#,
r#"
html! {
button {
@let this_variable_name_is_long_enough_to_cause_wrapping = some_object
.method()
.call()
.chain();
}
}
"#
);
test_default!(
control_let_medium_length_stays_inline,
r#"
html! { button { @let medium_var = some_object.method().call().chain(); } }
"#,
r#"
html! {
button {
@let medium_var = some_object.method().call().chain();
}
}
"#
);
test_default!(
control_for_range,
r##"
html!{ @for i in 0..10 { p { (i) } } }
"##,
r##"
html! {
@for i in 0..10 {
p { (i) }
}
}
"##
);
}