use crate::expression::{FieldExpression, OutputFormat};
use crate::{CsvSource, ElseTemplate, MacroInvocation, PivotSpec, RowTemplate, RowTemplateKind};
use convert_case::Case;
use proc_macro2::Ident;
use std::ops::Bound;
use syn::parse::{Parse, ParseStream};
use syn::token::Paren;
use syn::{Lit, LitStr, RangeLimits, Token, braced, parenthesized};
impl Parse for MacroInvocation {
fn parse(input: ParseStream) -> syn::Result<Self> {
let header = parse_header(input)?;
Ok(Self {
header,
template: input.parse()?,
})
}
}
fn parse_header(parser: ParseStream) -> syn::Result<CsvSource> {
let from = parser.parse()?;
let _comma = parser.parse::<Token![,]>()?;
let pivot = parser
.peek(super::kw::pivot)
.then(|| {
let pivot = parser.parse();
let _comma = parser.parse::<Token![,]>()?;
pivot
})
.transpose()?;
Ok(CsvSource { from, pivot })
}
impl Parse for PivotSpec {
fn parse(input: ParseStream) -> syn::Result<Self> {
let pivot = input.parse()?;
let content;
let parens = parenthesized!(content in input);
let column_from: Option<Lit> = content
.peek(syn::Lit)
.then(|| content.parse())
.transpose()?;
let range_limits = content.parse()?;
let column_to: Option<Lit> = content
.peek(syn::Lit)
.then(|| content.parse())
.transpose()?;
let _comma = content.parse::<Token![,]>()?;
let key_field_name = content.parse()?;
let _comma = content.parse::<Token![,]>()?;
let value_field_name = content.parse()?;
Ok(Self {
_kw: pivot,
_parens: parens,
column_from,
_range_limits: range_limits,
column_to: column_to
.map(|to| match range_limits {
RangeLimits::HalfOpen(_) => Bound::Excluded(to),
RangeLimits::Closed(_) => Bound::Included(to),
})
.unwrap_or(Bound::Unbounded),
key_field_name,
value_field_name,
})
}
}
impl Parse for RowTemplate {
fn parse(input: ParseStream) -> syn::Result<Self> {
let template;
if input.peek(syn::token::Brace) {
let _template_braces = syn::braced!(template in input);
let template = template.parse()?;
Ok(Self {
_hash: syn::parse_quote!(#), kind: RowTemplateKind::Plain, filter: None,
_template_braces,
template,
else_template: None,
})
} else {
let _hash = input.parse()?;
let kind = if input.peek(crate::kw::each) {
RowTemplateKind::Each(input.parse()?)
} else if input.peek(crate::kw::find) {
RowTemplateKind::Find(input.parse()?)
} else if input.peek(crate::kw::having) {
RowTemplateKind::Having(input.parse()?)
} else {
return Err(syn::Error::new(
input.span(),
"Expected 'each', 'find' or 'having'",
));
};
let filter = if input.peek(syn::token::Paren) {
Some(input.parse()?)
} else {
match kind {
RowTemplateKind::Find(_) | RowTemplateKind::Having(_) => {
return Err(syn::Error::new(
input.span(),
"#find or #having requires a condition. Use #find(condition) { ... } or #having(condition) { ... } or use #each for iteration",
));
}
RowTemplateKind::Each(_) | RowTemplateKind::Plain => None,
}
};
let _template_braces = syn::braced!(template in input);
let template = template.parse()?;
let else_template = ElseTemplate::try_parse(input)?;
if let RowTemplateKind::Having(_) = kind
&& let Some(else_template) = &else_template
{
return Err(syn::Error::new_spanned(
else_template._else_kw,
"#else is invalid following #having",
));
}
Ok(Self {
_hash,
kind,
filter,
_template_braces,
template,
else_template,
})
}
}
}
impl ElseTemplate {
fn try_parse(input: ParseStream) -> syn::Result<Option<Self>> {
if input.peek(Token![#]) && input.peek2(Token![else]) {
let template;
Ok(Some(Self {
_hash: input.parse()?,
_else_kw: input.parse()?,
_template_braces: syn::braced!(template in input),
template: template.parse()?,
}))
} else {
Ok(None)
}
}
}
mod kw {
syn::custom_keyword!(ident);
syn::custom_keyword!(Type);
syn::custom_keyword!(CONST);
}
impl Parse for FieldExpression {
fn parse(input: ParseStream) -> syn::Result<Self> {
let _hash: Token![#] = input.parse()?;
if input.peek(kw::ident) {
let _ident: kw::ident = input.parse()?;
let syntax_type = OutputFormat::IdentConverted(Case::Snake);
let content;
let _ = parenthesized!(content in input);
let prefix = content
.peek(syn::Ident)
.then(|| content.parse::<Ident>().map(|i| i.to_string()))
.transpose()?
.unwrap_or_default();
let field;
let _ = braced!(field in content);
let field = field.parse()?;
let suffix = content
.peek(syn::Ident)
.then(|| content.parse::<Ident>().map(|i| i.to_string()))
.transpose()?
.unwrap_or_default();
return Ok(Self {
field,
syntax_type,
prefix,
suffix,
});
}
if input.peek(kw::Type) {
let _ident: kw::Type = input.parse()?;
let syntax_type = OutputFormat::IdentConverted(Case::Pascal);
let content;
let _ = parenthesized!(content in input);
let prefix = content
.peek(syn::Ident)
.then(|| content.parse::<Ident>().map(|i| i.to_string()))
.transpose()?
.unwrap_or_default();
let field;
let _ = braced!(field in content);
let field = field.parse()?;
let suffix = content
.peek(syn::Ident)
.then(|| content.parse::<Ident>().map(|i| i.to_string()))
.transpose()?
.unwrap_or_default();
return Ok(Self {
field,
syntax_type,
prefix,
suffix,
});
}
if input.peek(kw::CONST) {
let _ident: kw::CONST = input.parse()?;
let syntax_type = OutputFormat::IdentConverted(Case::Constant);
let content;
let _ = parenthesized!(content in input);
let prefix = content
.peek(syn::Ident)
.then(|| content.parse::<Ident>().map(|i| i.to_string()))
.transpose()?
.unwrap_or_default();
let field;
let _ = braced!(field in content);
let field = field.parse()?;
let suffix = content
.peek(syn::Ident)
.then(|| content.parse::<Ident>().map(|i| i.to_string()))
.transpose()?
.unwrap_or_default();
return Ok(Self {
field,
syntax_type,
prefix,
suffix,
});
}
if input.peek(Paren) {
let syntax_type = OutputFormat::LitOrIdent;
let content;
let _ = parenthesized!(content in input);
if content.peek(LitStr) {
let lit_str: LitStr = content.parse()?;
let string = lit_str.value();
let mut split = string.split(['{', '}']);
let prefix = split.next().unwrap().to_string();
let mut field: syn::Ident =
syn::parse_str(split.next().expect("braces in string"))?;
field.set_span(lit_str.span());
let suffix = split.next().unwrap().to_string();
return Ok(Self {
field,
syntax_type: OutputFormat::LitStr,
prefix,
suffix,
});
} else {
let prefix = content
.peek(syn::Ident)
.then(|| content.parse::<Ident>().map(|i| i.to_string()))
.transpose()?
.unwrap_or_default();
let field;
let _ = braced!(field in content);
let field = field.parse()?;
let suffix = content
.peek(syn::Ident)
.then(|| content.parse::<Ident>().map(|i| i.to_string()))
.transpose()?
.unwrap_or_default();
return Ok(Self {
field,
syntax_type,
prefix,
suffix,
});
}
}
Err(syn::Error::new(input.span(), "Expected a replacement"))
}
}