use std::fmt::Write;
use quote::ToTokens;
use syn::ext::IdentExt as _;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use crate::{ast, lower_ast, ConcatInput, FormatArgsInput};
mod kw {
syn::custom_keyword!(DOCTYPE);
syn::custom_keyword!(doctype);
syn::custom_keyword!(html);
}
impl Parse for ast::DashIdent {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(Self(
Punctuated::<syn::Ident, syn::Token![-]>::parse_separated_nonempty_with(
input,
syn::Ident::parse_any,
)?
))
}
}
impl Parse for ast::Doctype {
fn parse(input: ParseStream) -> syn::Result<Self> {
input.parse::<syn::Token![<]>()?;
input.parse::<syn::Token![!]>()?;
if input.peek(kw::doctype) {
input.parse::<kw::doctype>()?;
} else {
input.parse::<kw::DOCTYPE>()?;
}
input.parse::<kw::html>()?;
input.parse::<syn::Token![>]>()?;
Ok(Self)
}
}
impl<Value: Parse> Parse for ast::Tag<Value> {
fn parse(input: ParseStream) -> syn::Result<Self> {
input.parse::<syn::Token![<]>()?;
if input.parse::<Option<syn::Token![/]>>()?.is_some() {
let name = input.parse()?;
input.parse::<syn::Token![>]>()?;
return Ok(Self::Closing { name });
}
let name = input.parse()?;
let mut attrs = Vec::new();
while !(input.peek(syn::Token![>])
|| (input.peek(syn::Token![/]) && input.peek2(syn::Token![>])))
{
attrs.push(input.parse()?);
}
let self_closing = input.parse::<Option<syn::Token![/]>>()?.is_some();
input.parse::<syn::Token![>]>()?;
Ok(Self::Opening {
name,
attrs,
self_closing,
})
}
}
impl<Value: Parse> Parse for ast::Attr<Value> {
fn parse(input: ParseStream) -> syn::Result<Self> {
let name = input.parse()?;
input.parse::<syn::Token![=]>()?;
let value = input.parse()?;
Ok(Self { name, value })
}
}
impl Parse for ast::LitValue {
fn parse(input: ParseStream) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(syn::LitStr) {
Ok(Self::LitStr(input.parse()?))
} else if lookahead.peek(syn::token::Brace) {
let content;
syn::braced!(content in input);
Ok(Self::Expr(content.parse()?))
} else {
Err(lookahead.error())
}
}
}
impl Parse for ast::PlaceholderValue {
fn parse(input: ParseStream) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(syn::LitStr) {
Ok(Self::LitStr(input.parse()?))
} else if lookahead.peek(syn::token::Brace) {
let content;
syn::braced!(content in input);
let value = content.parse()?;
let mut specs = None;
if content.parse::<Option<syn::Token![:]>>()?.is_some() {
specs = Some(content.parse()?);
}
Ok(Self::Expr { value, specs })
} else {
Err(lookahead.error())
}
}
}
impl<Value: Parse> Parse for ast::Node<Value> {
fn parse(input: ParseStream) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(syn::Token![<])
&& input.peek2(syn::Token![!])
&& (input.peek3(kw::DOCTYPE) || input.peek3(kw::doctype))
{
Ok(Self::Doctype(input.parse()?))
} else if lookahead.peek(syn::Token![<]) {
Ok(Self::Tag(input.parse()?))
} else if lookahead.peek(syn::LitStr)
|| lookahead.peek(syn::token::Brace)
{
Ok(Self::Value(input.parse()?))
} else {
Err(lookahead.error())
}
}
}
impl Parse for FormatArgsInput {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut template = String::new();
let mut values = Vec::new();
while !input.is_empty() {
let node = input.parse::<ast::Node<ast::PlaceholderValue>>()?;
values.extend(node.get_all_values().into_iter().map(Into::into));
for part in node.into_iter() {
let _ = template.write_str(&part.to_string());
}
}
Ok(Self { template, values })
}
}
impl Parse for ConcatInput {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut segments = Vec::new();
let mut acc = String::new();
while !input.is_empty() {
let node = input.parse::<ast::Node<ast::LitValue>>()?;
for part in node.into_iter() {
match part {
lower_ast::AstPart::AttrValue(v)
| lower_ast::AstPart::Value(v) => {
if let ast::LitValue::LitStr(lit) = v {
acc.push_str(&lit.value());
} else {
segments.push(acc.to_token_stream());
segments.push(v.to_token_stream());
acc.clear();
}
}
_ => {
let _ = write!(acc, "{}", part);
}
}
}
}
if !acc.is_empty() {
segments.push(acc.to_token_stream());
}
Ok(Self { segments })
}
}