#![allow(unused_imports, dead_code)]
use std::{
collections::{BTreeMap, BTreeSet},
iter,
path::PathBuf,
};
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::parse::ParseBuffer;
use syn::spanned::Spanned;
use yarte_codegen::{CodeGen, FmtCodeGen, HTMLCodeGen, TextCodeGen};
use yarte_helpers::{
config::{get_source, read_config_file, Config, PrintConfig},
logger::log,
};
use yarte_hir::{generate, visit_derive, HIROptions, Print, Struct};
use yarte_parser::{emitter, parse, parse_partials, source_map, Partial};
#[cfg(feature = "json")]
mod ser_json;
type Sources<'a> = &'a BTreeMap<PathBuf, String>;
macro_rules! build {
($i:ident, $codegen:ident, $opt:expr) => {{
let config_toml: &str = &read_config_file();
let config = &Config::new(config_toml);
let s = &match visit_derive($i, config) {
Ok(s) => s,
Err(ts) => return ts.into(),
};
proc_macro2::fallback::force();
let sources = &read(s.path.clone(), s.src.clone(), config);
sources_to_tokens(sources, config, s, $codegen(s), $opt)
}};
}
#[proc_macro_derive(TemplateText, attributes(template))]
pub fn template(input: TokenStream) -> TokenStream {
fn get_codegen<'a>(s: &'a Struct) -> Box<dyn CodeGen + 'a> {
Box::new(FmtCodeGen::new(TextCodeGen, s, "yarte"))
}
let i = &syn::parse(input).unwrap();
build!(
i,
get_codegen,
HIROptions {
is_text: true,
..Default::default()
}
)
.into()
}
#[proc_macro_derive(Template, attributes(template))]
pub fn template_html(input: TokenStream) -> TokenStream {
fn get_codegen<'a>(s: &'a Struct) -> Box<dyn CodeGen + 'a> {
Box::new(FmtCodeGen::new(HTMLCodeGen, s, "yarte"))
}
let i = &syn::parse(input).unwrap();
build!(i, get_codegen, Default::default()).into()
}
#[proc_macro_derive(TemplateFixedText, attributes(template))]
#[cfg(feature = "fixed")]
pub fn template_ptr(input: TokenStream) -> TokenStream {
fn get_codegen<'a>(s: &'a Struct) -> Box<dyn CodeGen + 'a> {
Box::new(yarte_codegen::FixedCodeGen::new(
yarte_codegen::TextFixedCodeGen("yarte"),
s,
"yarte",
))
}
let i = &syn::parse(input).unwrap();
build!(
i,
get_codegen,
HIROptions {
is_text: true,
..Default::default()
}
)
.into()
}
#[proc_macro_derive(TemplateFixed, attributes(template))]
#[cfg(feature = "fixed")]
pub fn template_html_ptr(input: TokenStream) -> TokenStream {
const PARENT: &str = "yarte";
fn get_codegen<'a>(s: &'a Struct) -> Box<dyn CodeGen + 'a> {
Box::new(yarte_codegen::FixedCodeGen::new(
yarte_codegen::HTMLFixedCodeGen(PARENT),
s,
PARENT,
))
}
let i = &syn::parse(input).unwrap();
build!(i, get_codegen, Default::default()).into()
}
#[proc_macro_derive(TemplateBytesText, attributes(template))]
#[cfg(feature = "bytes-buf")]
pub fn template_bytes(input: TokenStream) -> TokenStream {
const PARENT: &str = "yarte";
let buf_i = format_ident!("bytes_mut");
let buf: syn::Expr = syn::parse2(quote!(#buf_i)).unwrap();
let get_codegen = |s| {
Box::new(yarte_codegen::BytesCodeGen::new(
yarte_codegen::TextBytesCodeGen::new(&buf),
s,
buf_i,
PARENT,
))
};
let i = &syn::parse(input).unwrap();
build!(
i,
get_codegen,
HIROptions {
is_text: true,
..Default::default()
}
)
.into()
}
#[proc_macro_derive(TemplateBytes, attributes(template))]
#[cfg(feature = "bytes-buf")]
pub fn template_html_bytes(input: TokenStream) -> TokenStream {
const PARENT: &str = "yarte";
let buf_i = format_ident!("bytes_mut");
let buf: syn::Expr = syn::parse2(quote!(#buf_i)).unwrap();
let get_codegen = |s| {
Box::new(yarte_codegen::BytesCodeGen::new(
yarte_codegen::HTMLBytesCodeGen::new(&buf),
s,
buf_i,
PARENT,
))
};
let i = &syn::parse(input).unwrap();
build!(i, get_codegen, Default::default()).into()
}
#[proc_macro_derive(TemplateFixedMin, attributes(template))]
#[cfg(all(feature = "html-min", feature = "fixed"))]
pub fn template_html_min_ptr(input: TokenStream) -> TokenStream {
const PARENT: &str = "yarte";
fn get_codegen<'a>(s: &'a Struct) -> Box<dyn CodeGen + 'a> {
Box::new(yarte_codegen::FixedCodeGen::new(
yarte_codegen::HTMLMinFixedCodeGen(PARENT),
s,
PARENT,
))
}
let i = &syn::parse(input).unwrap();
build!(i, get_codegen, Default::default()).into()
}
#[proc_macro_derive(TemplateBytesMin, attributes(template))]
#[cfg(all(feature = "html-min", feature = "bytes-buf"))]
pub fn template_html_min_bytes(input: TokenStream) -> TokenStream {
const PARENT: &str = "yarte";
let buf_i = format_ident!("bytes_mut");
let buf: syn::Expr = syn::parse2(quote!(#buf_i)).unwrap();
let get_codegen = |s| {
Box::new(yarte_codegen::BytesCodeGen::new(
yarte_codegen::HTMLMinBytesCodeGen::new(&buf),
s,
buf_i,
PARENT,
))
};
let i = &syn::parse(input).unwrap();
build!(i, get_codegen, Default::default()).into()
}
#[proc_macro_derive(TemplateMin, attributes(template))]
#[cfg(feature = "html-min")]
pub fn template_html_min(input: TokenStream) -> TokenStream {
fn get_codegen<'a>(s: &'a Struct) -> Box<dyn CodeGen + 'a> {
Box::new(FmtCodeGen::new(yarte_codegen::HTMLMinCodeGen, s, "yarte"))
}
let i = &syn::parse(input).unwrap();
build!(i, get_codegen, Default::default()).into()
}
#[proc_macro_derive(App, attributes(template, msg, inner))]
#[cfg(feature = "wasm-app")]
pub fn app(input: TokenStream) -> TokenStream {
fn get_codegen<'a>(s: &'a Struct) -> Box<dyn CodeGen + 'a> {
Box::new(yarte_codegen::client::WASMCodeGen::new(s))
}
let i = &syn::parse(input).unwrap();
let config_toml: &str = &read_config_file();
let config = &Config::new(config_toml);
let s = &match visit_derive(i, config) {
Ok(s) => s,
Err(tt) => return tt.into(),
};
let sources = &read(s.path.clone(), s.src.clone(), config);
sources_to_tokens(sources, config, s, get_codegen(s), Default::default()).into()
}
#[proc_macro_derive(TemplateWasmServer, attributes(template))]
#[cfg(feature = "wasm-server")]
pub fn template_wasm_server(input: TokenStream) -> TokenStream {
const PARENT: &str = "yarte";
let buf_i = format_ident!("bytes_mut");
let buf: syn::Expr = syn::parse2(quote!(#buf_i)).unwrap();
let get_codegen = |s| {
Box::new(yarte_codegen::BytesCodeGen::new(
yarte_codegen::server::WASMCodeGen::new(s, &buf),
s,
buf_i,
PARENT,
))
};
let i = &syn::parse(input).unwrap();
build!(i, get_codegen, Default::default()).into()
}
#[proc_macro_derive(Serialize)]
#[cfg(feature = "json")]
pub fn serialize_json(i: TokenStream) -> TokenStream {
let i = syn::parse(i).unwrap();
let tokens = ser_json::serialize_json(i);
tokens.into()
}
#[proc_macro]
pub fn yformat_html(i: TokenStream) -> TokenStream {
const PARENT: &str = "yarte";
fn get_codegen<'a>(_s: &'a Struct<'a>) -> Box<dyn CodeGen + 'a> {
Box::new(yarte_codegen::FnFmtCodeGen::new(HTMLCodeGen, PARENT))
}
let src: syn::LitStr = syn::parse(i).unwrap();
let input = quote! {
#[template(src = #src)]
struct __Main__;
};
let i = &syn::parse2(input).unwrap();
build!(
i,
get_codegen,
HIROptions {
resolve_to_self: false,
parent: PARENT,
..Default::default()
}
)
.into()
}
#[proc_macro]
pub fn yformat(i: TokenStream) -> TokenStream {
const PARENT: &str = "yarte";
fn get_codegen<'a>(_s: &'a Struct<'a>) -> Box<dyn CodeGen + 'a> {
Box::new(yarte_codegen::FnFmtCodeGen::new(TextCodeGen, PARENT))
}
let src: syn::LitStr = syn::parse(i).unwrap();
let input = quote! {
#[template(src = #src)]
struct __Main__;
};
let i = &syn::parse2(input).unwrap();
build!(
i,
get_codegen,
HIROptions {
resolve_to_self: false,
is_text: true,
parent: PARENT,
}
)
.into()
}
struct AutoArg {
path: syn::Ident,
_a: syn::Token![!],
_b: syn::token::Paren,
ty: syn::Type,
_c: syn::token::Comma,
lit: syn::LitStr,
}
#[allow(clippy::eval_order_dependence)]
impl syn::parse::Parse for AutoArg {
fn parse(input: &ParseBuffer) -> syn::Result<Self> {
let content;
Ok(AutoArg {
path: input.parse()?,
_a: input.parse()?,
_b: syn::parenthesized!(content in input),
ty: content.parse()?,
_c: content.parse()?,
lit: content.parse()?,
})
}
}
#[proc_macro]
pub fn auto(i: TokenStream) -> TokenStream {
let AutoArg { path, ty, lit, .. } = match syn::parse(i) {
Ok(arg) => arg,
Err(e) => return e.to_compile_error().into(),
};
let token = quote! {{
thread_local! {
static SIZE: std::cell::Cell<usize> = std::cell::Cell::new(0);
}
let mut __buf: #ty = yarte::Buffer::with_capacity(SIZE.with(|v| v.get()));
#path!(__buf, #lit);
SIZE.with(|v| if v.get() < __buf.len() {
v.set(__buf.len())
});
__buf
}};
token.into()
}
#[proc_macro]
#[cfg(feature = "bytes-buf")]
pub fn ywrite(i: TokenStream) -> TokenStream {
const PARENT: &str = "yarte";
let WriteArg { buf, src, .. } = match syn::parse(i) {
Ok(arg) => arg,
Err(e) => return e.to_compile_error().into(),
};
{
use syn::Expr::*;
match &buf {
Field(_) | Path(_) => (),
_ => {
return syn::Error::new(buf.span(), "first argument should be a ident or field")
.to_compile_error()
.into()
}
}
}
let get_codegen = |_| {
Box::new(yarte_codegen::WriteBCodeGen::new(
yarte_codegen::TextBytesCodeGen::new(&buf),
PARENT,
))
};
let input = quote! {
#[template(src = #src)]
struct __Main__;
};
let i = &syn::parse2(input).unwrap();
build!(
i,
get_codegen,
HIROptions {
resolve_to_self: false,
is_text: true,
parent: PARENT,
}
)
.into()
}
#[proc_macro]
#[cfg(feature = "bytes-buf")]
pub fn ywrite_html(i: TokenStream) -> TokenStream {
const PARENT: &str = "yarte";
let WriteArg { buf, src, .. } = match syn::parse(i) {
Ok(arg) => arg,
Err(e) => return e.to_compile_error().into(),
};
{
use syn::Expr::*;
match &buf {
Field(_) | Path(_) => (),
_ => {
return syn::Error::new(buf.span(), "first argument should be a ident or field")
.to_compile_error()
.into()
}
}
}
let get_codegen = |_| {
Box::new(yarte_codegen::WriteBCodeGen::new(
yarte_codegen::HTMLBytesCodeGen::new(&buf),
PARENT,
))
};
let input = quote! {
#[template(src = #src)]
struct __Main__;
};
let i = &syn::parse2(input).unwrap();
build!(
i,
get_codegen,
HIROptions {
resolve_to_self: false,
parent: PARENT,
..Default::default()
}
)
.into()
}
#[proc_macro]
#[cfg(all(feature = "html-min", feature = "bytes-buf"))]
pub fn ywrite_min(i: TokenStream) -> TokenStream {
const PARENT: &str = "yarte";
let WriteArg { buf, src, .. } = match syn::parse(i) {
Ok(arg) => arg,
Err(e) => return e.to_compile_error().into(),
};
{
use syn::Expr::*;
match &buf {
Field(_) | Path(_) => (),
_ => {
return syn::Error::new(buf.span(), "first argument should be a ident or field")
.to_compile_error()
.into()
}
}
}
let get_codegen = |_| {
Box::new(yarte_codegen::WriteBCodeGen::new(
yarte_codegen::HTMLMinBytesCodeGen::new(&buf),
PARENT,
))
};
let input = quote! {
#[template(src = #src)]
struct __Main__;
};
let i = &syn::parse2(input).unwrap();
build!(
i,
get_codegen,
HIROptions {
resolve_to_self: false,
parent: PARENT,
..Default::default()
}
)
.into()
}
#[proc_macro_attribute]
#[cfg(feature = "bytes-buf")]
pub fn html(args: TokenStream, input: TokenStream) -> TokenStream {
const PARENT: &str = "yarte";
let args_is_empty = args.is_empty();
let buf: syn::Expr = if args_is_empty {
syn::parse_str("__buf").unwrap()
} else {
match syn::parse(args) {
Ok(i) => i,
Err(e) => return e.into_compile_error().into(),
}
};
{
use syn::Expr::*;
match &buf {
Field(_) | Path(_) => (),
_ => {
return syn::Error::new(buf.span(), "first argument should be a ident or field")
.to_compile_error()
.into()
}
}
}
let get_codegen = |_| {
Box::new(yarte_codegen::AttrBCodeGen::new(
yarte_codegen::HTMLBytesCodeGen::new(&buf),
PARENT,
!args_is_empty,
))
};
let input: syn::LitStr = match syn::parse(input) {
Ok(i) => i,
Err(e) => return e.into_compile_error().into(),
};
let input = quote! {
#[template(src = #input)]
struct __Main__;
};
let i = &syn::parse2(input).unwrap();
let code = build!(
i,
get_codegen,
HIROptions {
resolve_to_self: false,
parent: PARENT,
..Default::default()
}
);
code.into()
}
struct WriteArg {
buf: syn::Expr,
_comma: syn::Token![,],
src: syn::LitStr,
_end_comma: Option<syn::Token![,]>,
}
impl syn::parse::Parse for WriteArg {
fn parse(input: &ParseBuffer) -> syn::parse::Result<Self> {
Ok(WriteArg {
buf: input.parse()?,
_comma: input.parse()?,
src: input.parse()?,
_end_comma: input.parse()?,
})
}
}
fn sources_to_tokens<'a>(
sources: Sources,
config: &Config,
s: &'a Struct<'a>,
mut codegen: Box<dyn CodeGen + 'a>,
opt: HIROptions,
) -> proc_macro2::TokenStream {
let mut parsed = BTreeMap::new();
for (p, src) in sources {
let nodes = match parse(source_map::get_cursor(p, src)) {
Ok(n) => n,
Err(e) => emitter(sources, config, iter::once(e)),
};
parsed.insert(p, nodes);
}
if cfg!(debug_assertions) && config.print_override == PrintConfig::Ast
|| config.print_override == PrintConfig::All
|| s.print == Print::Ast
|| s.print == Print::All
{
eprintln!("{:?}\n", parsed);
}
let hir = generate(config, s, &parsed, opt)
.unwrap_or_else(|e| emitter(sources, config, e.into_iter()));
source_map::clean();
let tokens = codegen.gen(hir);
if cfg!(debug_assertions) && config.print_override == PrintConfig::Code
|| config.print_override == PrintConfig::All
|| s.print == Print::Code
|| s.print == Print::All
{
log(&tokens.to_string());
}
tokens
}
fn read(path: PathBuf, src: String, config: &Config) -> BTreeMap<PathBuf, String> {
let mut stack = vec![(path, src)];
let mut visited = BTreeMap::new();
while let Some((path, src)) = stack.pop() {
let partials = parse_partials(&src);
let partials = match partials {
Ok(n) => n
.iter()
.map(|Partial(_, partial, _)| config.resolve_partial(&path, partial.t()))
.collect::<BTreeSet<_>>(),
Err(e) => {
visited.insert(path, src);
emitter(&visited, config, iter::once(e))
}
};
visited.insert(path, src);
for partial in partials {
if !visited.contains_key(&partial) {
let src = get_source(partial.as_path());
stack.push((partial, src));
}
}
}
visited
}