use proc_macro2::{Span, TokenStream, TokenTree};
use syn::parse::{Parse, ParseStream, Result};
use syn::spanned::Spanned;
use syn::token::Brace;
use syn::{braced, Block, Expr, Ident, Token};
#[derive(Debug)]
pub enum Tag {
Open {
name: Ident,
attrs: Vec<Attr>,
open_bracket_span: Span,
closing_bracket_span: Span,
is_self_closing: bool,
},
Close {
name: Ident,
first_angle_bracket_span: Span,
},
Text {
text: String,
start_span: Option<Span>,
end_span: Option<Span>,
},
Braced { block: Box<Block>, brace_span: Span },
}
#[derive(Debug, Eq, PartialEq)]
pub enum TagKind {
Open,
Close,
Text,
Braced,
}
#[derive(Debug)]
pub struct Attr {
pub key: Ident,
pub value: Expr,
}
impl Parse for Tag {
fn parse(input: ParseStream) -> Result<Self> {
let mut input = input;
if input.peek(Token![<]) {
let first_angle_bracket_span = input.parse::<Token![<]>()?;
let first_angle_bracket_span = first_angle_bracket_span.span();
let optional_close: Option<Token![/]> = input.parse()?;
let is_open_tag = optional_close.is_none();
if is_open_tag {
return parse_open_tag(&mut input, first_angle_bracket_span);
} else {
return parse_close_tag(&mut input, first_angle_bracket_span);
}
}
if input.peek(Brace) {
return parse_block(&mut input);
}
return parse_text_node(&mut input);
}
}
fn parse_open_tag(input: &mut ParseStream, open_bracket_span: Span) -> Result<Tag> {
let name: Ident = input.parse()?;
let attrs = parse_attributes(input)?;
let is_self_closing: Option<Token![/]> = input.parse()?;
let is_self_closing = is_self_closing.is_some();
let closing_bracket = input.parse::<Token![>]>()?;
let closing_bracket_span = closing_bracket.span();
Ok(Tag::Open {
name,
attrs,
open_bracket_span,
closing_bracket_span,
is_self_closing,
})
}
fn parse_attributes(input: &mut ParseStream) -> Result<Vec<Attr>> {
let mut attrs = Vec::new();
while input.peek(Ident)
|| input.peek(Token![async])
|| input.peek(Token![for])
|| input.peek(Token![loop])
|| input.peek(Token![type])
{
let maybe_async_key: Option<Token![async]> = input.parse()?;
let maybe_for_key: Option<Token![for]> = input.parse()?;
let maybe_loop_key: Option<Token![loop]> = input.parse()?;
let maybe_type_key: Option<Token![type]> = input.parse()?;
let key = if maybe_async_key.is_some() {
Ident::new("async", maybe_async_key.unwrap().span())
} else if maybe_for_key.is_some() {
Ident::new("for", maybe_for_key.unwrap().span())
} else if maybe_loop_key.is_some() {
Ident::new("loop", maybe_loop_key.unwrap().span())
} else if maybe_type_key.is_some() {
Ident::new("type", maybe_type_key.unwrap().span())
} else {
input.parse()?
};
input.parse::<Token![=]>()?;
let mut value_tokens = TokenStream::new();
loop {
let tt: TokenTree = input.parse()?;
value_tokens.extend(Some(tt));
let has_attrib_key = input.peek(Ident)
|| input.peek(Token![async])
|| input.peek(Token![for])
|| input.peek(Token![loop])
|| input.peek(Token![type]);
let peek_start_of_next_attr = has_attrib_key && input.peek2(Token![=]);
let peek_end_of_tag = input.peek(Token![>]);
let peek_self_closing = input.peek(Token![/]);
if peek_end_of_tag || peek_start_of_next_attr || peek_self_closing {
break;
}
}
let value: Expr = syn::parse2(value_tokens)?;
attrs.push(Attr { key, value });
}
Ok(attrs)
}
fn parse_close_tag(input: &mut ParseStream, first_angle_bracket_span: Span) -> Result<Tag> {
let name: Ident = input.parse()?;
input.parse::<Token![>]>()?;
Ok(Tag::Close {
name,
first_angle_bracket_span,
})
}
fn parse_block(input: &mut ParseStream) -> Result<Tag> {
let content;
let brace_token = braced!(content in input);
let brace_span = brace_token.span;
let block_expr = content.call(Block::parse_within)?;
let block = Box::new(Block {
brace_token,
stmts: block_expr,
});
Ok(Tag::Braced { block, brace_span })
}
fn parse_text_node(input: &mut ParseStream) -> Result<Tag> {
let _text_tokens = TokenStream::new();
let mut text = "".to_string();
let mut idx = 0;
let mut start_span = None;
let mut most_recent_span: Option<Span> = None;
loop {
if input.is_empty() {
break;
}
let tt: TokenTree = input.parse()?;
if idx == 0 {
start_span = Some(tt.span());
most_recent_span = Some(tt.span());
}
if idx != 0 {
if let Some(most_recent_span) = most_recent_span {
let current_span_start = tt.span().start();
let most_recent_span_end = most_recent_span.end();
let spans_on_different_lines = current_span_start.line != most_recent_span_end.line;
let span_comes_before_previous_span = current_span_start.column
< most_recent_span_end.column
&& !spans_on_different_lines;
if spans_on_different_lines {
text += " ";
} else if !span_comes_before_previous_span
&& current_span_start.column - most_recent_span_end.column > 0
{
text += " ";
}
}
}
text += &tt.to_string();
most_recent_span = Some(tt.span());
let peek_closing_tag = input.peek(Token![<]);
let peek_start_block = input.peek(Brace);
if peek_closing_tag || peek_start_block {
break;
}
idx += 1;
}
Ok(Tag::Text {
text,
start_span,
end_span: most_recent_span,
})
}