use proc_macro2::{Ident, TokenStream};
use syn::parse::{Parse, ParseStream};
use syn::{Error, LitBool, Path, Result, Token};
use template_quote::{quote, ToTokens};
#[derive(Clone)]
pub struct Check {
pub path: Path,
pub args: Option<TokenStream>,
}
impl ToTokens for Check {
fn to_tokens(&self, tokens: &mut TokenStream) {
let p = &self.path;
match &self.args {
Some(a) => tokens.extend(quote! { #p(#a) }),
None => tokens.extend(quote! { #p }),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Layout {
Auto,
Single,
Direct,
}
#[derive(Clone)]
#[non_exhaustive]
pub struct Options {
pub macro_name: Option<Ident>,
pub mock: Option<Path>,
pub repeater: Option<Path>,
pub allow_primitive: bool,
pub allow_std_prelude: bool,
pub krate: Option<Path>,
pub emit_item: bool,
pub layout: Layout,
pub docs: bool,
pub(crate) derived: bool,
pub(crate) given: Vec<String>,
pub checks: Vec<Check>,
pub metas: Vec<Check>,
}
impl Default for Options {
fn default() -> Self {
Options {
macro_name: None,
mock: None,
repeater: None,
allow_primitive: true,
allow_std_prelude: false,
krate: None,
emit_item: true,
layout: Layout::Auto,
docs: false,
derived: false,
given: Vec::new(),
checks: Vec::new(),
metas: Vec::new(),
}
}
}
impl Options {
pub fn krate(&self) -> Path {
self.krate
.clone()
.unwrap_or_else(crate::cratepath::default_crate_path)
}
}
impl ToTokens for Options {
fn to_tokens(&self, tokens: &mut TokenStream) {
let mut parts: Vec<TokenStream> = Vec::new();
if let Some(k) = &self.krate {
parts.push(quote! { crate = #k });
}
if let Some(n) = &self.macro_name {
parts.push(quote! { macro_name = #n });
}
if let Some(m) = &self.mock {
parts.push(quote! { mock = #m });
}
if let Some(r) = &self.repeater {
parts.push(quote! { repeater = #r });
}
if !self.allow_primitive {
parts.push(quote! { allow_primitive = false });
}
if self.allow_std_prelude {
parts.push(quote! { allow_std_prelude = true });
}
if !self.emit_item {
parts.push(quote! { emit_item = false });
}
match self.layout {
Layout::Auto => {}
Layout::Single => parts.push(quote! { layout = single }),
Layout::Direct => parts.push(quote! { layout = direct }),
}
if self.docs {
parts.push(quote! { docs = true });
}
for m in &self.metas {
parts.push(quote! { meta = #m });
}
for c in &self.checks {
parts.push(quote! { #c });
}
tokens.extend(quote! { #(#parts),* });
}
}
impl Parse for Options {
fn parse(input: ParseStream) -> Result<Self> {
let mut opts = Options::default();
while !input.is_empty() {
if input.peek(Token![crate]) && input.peek2(Token![=]) {
input.parse::<Token![crate]>()?;
input.parse::<Token![=]>()?;
opts.krate = Some(Path::parse_mod_style(input)?);
} else if input.peek(syn::Ident) && input.peek2(Token![=]) && !input.peek2(Token![==]) {
let key: Ident = input.parse()?;
input.parse::<Token![=]>()?;
opts.given.push(key.to_string());
match key.to_string().as_str() {
"macro_name" => opts.macro_name = Some(input.parse()?),
"export_name" | "marker" => {
return Err(Error::new(
key.span(),
if key == "export_name" {
"`export_name` was removed: the hidden macro name is generated; \
use `macro_name = ..` to choose the public name"
} else {
"`marker` was removed: the `Announce` impl follows the item \
(none with `emit_item = false` or `mock`); use `#[derive(Announce)]` \
to announce a struct, enum or union and implement it"
},
))
}
"mock" => opts.mock = Some(Path::parse_mod_style(input)?),
"repeater" => {
let p = Path::parse_mod_style(input)?;
crate::leak::check_repeater(&p)?;
opts.repeater = Some(p);
}
"signature_only" | "transform" => {
return Err(Error::new(
key.span(),
format!(
"`{}` is not a definition-side option: transformers are chosen by the caller, \
e.g. `F! {{ [F!, ::announce::signature_only!, callback!] .. }}`",
key
),
))
}
"emit_item" => opts.emit_item = input.parse::<LitBool>()?.value,
"layout" => {
let v: Ident = input.parse()?;
opts.layout = match v.to_string().as_str() {
"auto" => Layout::Auto,
"single" => Layout::Single,
"direct" => Layout::Direct,
_ => {
return Err(Error::new(
v.span(),
"`layout` is `auto`, `single` or `direct`",
))
}
};
}
"docs" => opts.docs = input.parse::<LitBool>()?.value,
"allow_primitive" => opts.allow_primitive = input.parse::<LitBool>()?.value,
"allow_std_prelude" => opts.allow_std_prelude = input.parse::<LitBool>()?.value,
"meta" => {
let path = Path::parse_mod_style(input)?;
let args = if input.peek(syn::token::Paren) {
let content;
syn::parenthesized!(content in input);
Some(content.parse::<TokenStream>()?)
} else {
None
};
opts.metas.push(Check { path, args });
}
_ => {
return Err(Error::new(
key.span(),
"unknown announce option; expected one of `macro_name`, \
`mock`, `repeater`, `allow_primitive`, `allow_std_prelude`, `crate`, `emit_item`, `layout`, `docs`, `meta` (a bare path is a check)",
))
}
}
} else {
let path = Path::parse_mod_style(input)?;
let args = if input.peek(syn::token::Paren) {
let content;
syn::parenthesized!(content in input);
Some(content.parse::<TokenStream>()?)
} else {
None
};
opts.checks.push(Check { path, args });
}
if input.is_empty() {
break;
}
input.parse::<Token![,]>()?;
}
Ok(opts)
}
}
pub struct CaptureArgs {
pub random: u64,
pub owned: crate::helper::OwnedAttrs,
pub opts: Options,
}
impl Parse for CaptureArgs {
fn parse(input: ParseStream) -> Result<Self> {
let lit: syn::LitInt = input.parse()?;
input.parse::<Token![;]>()?;
let owned = if input.peek(syn::token::Bracket) {
let content;
syn::bracketed!(content in input);
let names: syn::punctuated::Punctuated<Ident, Token![,]> =
syn::punctuated::Punctuated::parse_terminated(&content)?;
input.parse::<Token![;]>()?;
crate::helper::OwnedAttrs::from_idents(names)
} else {
crate::helper::OwnedAttrs::new()
};
Ok(CaptureArgs {
random: lit.base10_parse()?,
owned,
opts: input.parse()?,
})
}
}