mirror of
https://git.asonix.dog/asonix/activitystreams.git
synced 2024-06-03 05:49:25 +00:00
1522 lines
54 KiB
Rust
1522 lines
54 KiB
Rust
/*
|
|
* This file is part of ActivityStreams Derive.
|
|
*
|
|
* Copyright © 2020 Riley Trautman
|
|
*
|
|
* ActivityStreams Derive is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* ActivityStreams Derive is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with ActivityStreams Derive. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
//! Derive macros for Activity Streams
|
|
//!
|
|
//! ## Examples
|
|
//!
|
|
//! First, add `serde` and `activitystreams-derive` to your Cargo.toml
|
|
//! ```toml
|
|
//! activitystreams-derive = "0.5.0"
|
|
//! # or activitystreams = "0.5.0"
|
|
//! serde = { version = "1.0", features = ["derive"] }
|
|
//! ```
|
|
//!
|
|
//! ```rust
|
|
//! use activitystreams_derive::{properties, UnitString};
|
|
//! // or activitystreams::{properties, UnitString};
|
|
//! use serde_json::Value;
|
|
//!
|
|
//! /// Using the UnitString derive macro
|
|
//! ///
|
|
//! /// This macro implements Serialize and Deserialize for the given type, making this type
|
|
//! /// represent the string "SomeKind" in JSON.
|
|
//! #[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, UnitString)]
|
|
//! #[unit_string(SomeKind)]
|
|
//! pub struct MyKind;
|
|
//!
|
|
//! /// Using the properties macro
|
|
//! ///
|
|
//! /// This macro generates getters and setters for the associated fields.
|
|
//! properties! {
|
|
//! My {
|
|
//! context {
|
|
//! types [
|
|
//! String,
|
|
//! ],
|
|
//! rename("@context"),
|
|
//! },
|
|
//! kind {
|
|
//! types [
|
|
//! MyKind,
|
|
//! ],
|
|
//! functional,
|
|
//! required,
|
|
//! rename("type"),
|
|
//! },
|
|
//! required_key {
|
|
//! types [
|
|
//! Value,
|
|
//! ],
|
|
//! functional,
|
|
//! required,
|
|
//! alias [
|
|
//! "someKey",
|
|
//! "existingKey",
|
|
//! "woooKey",
|
|
//! ],
|
|
//! },
|
|
//! }
|
|
//! }
|
|
//!
|
|
//! fn main () -> Result<(), Box<dyn std::error::Error>> {
|
|
//! let s = r#"{
|
|
//! "@context": "http://www.w3c.org/ns#activitystreams",
|
|
//! "type": "SomeKind",
|
|
//! "woooKey": {
|
|
//! "key": "value"
|
|
//! }
|
|
//! }"#;
|
|
//!
|
|
//! let m: MyProperties = serde_json::from_str(s)?;
|
|
//! assert_eq!(&MyKind, m.get_kind());
|
|
//! Ok(())
|
|
//! }
|
|
//! ```
|
|
|
|
extern crate proc_macro;
|
|
|
|
use proc_macro::TokenStream;
|
|
use proc_macro2::TokenTree;
|
|
use quote::{quote, ToTokens};
|
|
use syn::{
|
|
braced, bracketed, parenthesized,
|
|
parse::{Parse, ParseStream, Peek},
|
|
parse_macro_input,
|
|
punctuated::Punctuated,
|
|
token, Attribute, Data, DeriveInput, Fields, Ident, LitStr, Result, Token, Type,
|
|
};
|
|
|
|
/// Generate a type with default extensions
|
|
///
|
|
/// This derive
|
|
/// ```ignore
|
|
/// use activitystreams::{extensions::Ext, Extensible};
|
|
///
|
|
/// #[derive(Clone, Debug, Default, Extensible)]
|
|
/// #[extension(MyExtension)]
|
|
/// #[extension(MyOtherExtension)]
|
|
/// pub struct MyType;
|
|
/// ```
|
|
///
|
|
/// Produces this code
|
|
/// ```ignore
|
|
/// impl MyType {
|
|
/// pub fn full() -> Ext<Ext<MyType, MyExtension>, OtherExtension> {
|
|
/// Default::default()
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
#[proc_macro_derive(Extensible, attributes(extension))]
|
|
pub fn extensible(input: TokenStream) -> TokenStream {
|
|
let input: DeriveInput = syn::parse(input).unwrap();
|
|
|
|
let name = input.ident;
|
|
|
|
let kind: proc_macro2::TokenStream = input
|
|
.attrs
|
|
.iter()
|
|
.filter_map(move |attr| {
|
|
if attr
|
|
.path
|
|
.segments
|
|
.last()
|
|
.map(|segment| segment.ident == "extension")
|
|
.unwrap_or(false)
|
|
{
|
|
Some(from_value(attr.clone()))
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.fold(quote! {#name}, |acc, ident| {
|
|
quote! { Ext<#acc, #ident> }
|
|
});
|
|
|
|
let tokens = quote! {
|
|
impl #name {
|
|
/// Generate a fully extended type
|
|
///
|
|
/// This effect can be achieved with `Self::new().extend(SomeExtension::default())`
|
|
pub fn full() -> #kind {
|
|
Default::default()
|
|
}
|
|
}
|
|
};
|
|
|
|
tokens.into()
|
|
}
|
|
|
|
/// Derive implementations for activitystreams objects
|
|
///
|
|
/// ```ignore
|
|
/// #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PropRefs)]
|
|
/// #[prop_refs(Object)]
|
|
/// pub struct MyStruct {
|
|
/// /// Derive AsRef<MyProperties> and AsMut<MyProperties> delegating to `my_field`
|
|
/// #[prop_refs]
|
|
/// my_field: MyProperties,
|
|
///
|
|
/// /// Derive the above, plus Object (activitystreams)
|
|
/// #[prop_refs]
|
|
/// obj_field: ObjectProperties,
|
|
/// }
|
|
/// ```
|
|
#[proc_macro_derive(PropRefs, attributes(prop_refs))]
|
|
pub fn ref_derive(input: TokenStream) -> TokenStream {
|
|
let input: DeriveInput = syn::parse(input).unwrap();
|
|
|
|
let name = input.ident;
|
|
|
|
let data = match input.data {
|
|
Data::Struct(s) => s,
|
|
_ => panic!("Can only derive for structs"),
|
|
};
|
|
|
|
let fields = match data.fields {
|
|
Fields::Named(fields) => fields,
|
|
_ => panic!("Can only derive for named fields"),
|
|
};
|
|
|
|
let name2 = name.clone();
|
|
let base_impl = quote! {
|
|
impl Base for #name {}
|
|
|
|
impl #name {
|
|
/// Create from default
|
|
pub fn new() -> Self {
|
|
Default::default()
|
|
}
|
|
}
|
|
|
|
impl std::convert::TryFrom<#name> for BaseBox {
|
|
type Error = std::io::Error;
|
|
|
|
fn try_from(s: #name) -> Result<Self, Self::Error> {
|
|
BaseBox::from_concrete(s)
|
|
}
|
|
}
|
|
};
|
|
let trait_impls: proc_macro2::TokenStream = input
|
|
.attrs
|
|
.iter()
|
|
.filter_map(move |attr| {
|
|
if attr
|
|
.path
|
|
.segments
|
|
.last()
|
|
.map(|segment| segment.ident == "prop_refs")
|
|
.unwrap_or(false)
|
|
{
|
|
let object = from_value(attr.clone());
|
|
let name = name2.clone();
|
|
let box_name = Ident::new(&format!("{}Box", object), name.span());
|
|
|
|
Some(quote! {
|
|
impl #object for #name {}
|
|
|
|
impl std::convert::TryFrom<#name> for #box_name {
|
|
type Error = std::io::Error;
|
|
|
|
fn try_from(s: #name) -> Result<Self, Self::Error> {
|
|
#box_name::from_concrete(s)
|
|
}
|
|
}
|
|
})
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
let tokens: proc_macro2::TokenStream = fields
|
|
.named
|
|
.iter()
|
|
.filter_map(|field| {
|
|
let our_attr = field.attrs.iter().find(|attribute| {
|
|
attribute
|
|
.path
|
|
.segments
|
|
.last()
|
|
.map(|segment| segment.ident == "prop_refs")
|
|
.unwrap_or(false)
|
|
});
|
|
|
|
our_attr.map(move |_| (field.ident.clone().unwrap(), field.ty.clone()))
|
|
})
|
|
.map(move |(ident, ty)| {
|
|
let name = name.clone();
|
|
quote! {
|
|
impl AsRef<#ty> for #name {
|
|
fn as_ref(&self) -> &#ty {
|
|
&self.#ident
|
|
}
|
|
}
|
|
|
|
impl AsMut<#ty> for #name {
|
|
fn as_mut(&mut self) -> &mut #ty {
|
|
&mut self.#ident
|
|
}
|
|
}
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
let full = quote! {
|
|
#base_impl
|
|
#trait_impls
|
|
#tokens
|
|
};
|
|
|
|
full.into()
|
|
}
|
|
|
|
/// Derive a wrapper type based on serde_json::Value to contain any possible trait type
|
|
///
|
|
/// The following code
|
|
/// ```ignore
|
|
/// #[wrapper_type]
|
|
/// pub trait Object {}
|
|
/// ```
|
|
/// produces the following type
|
|
/// ```ignore
|
|
/// pub struct ObjectBox(pub serde_json::Value);
|
|
///
|
|
/// impl ObjectBox {
|
|
/// pub fn from_concrete<T>(t: T) -> Result<Self, std::io::Error>
|
|
/// where
|
|
/// T: Object + serde::ser::Serialize;
|
|
///
|
|
/// pub fn into_concrete<T>(self) -> Result<T, std::io::Error>
|
|
/// where
|
|
/// T: Object + serde::de::DeserializeOwned;
|
|
///
|
|
/// pub fn is_type(&self, kind: impl std::fmt::Display) -> bool;
|
|
///
|
|
/// pub fn type(&self) -> Option<&str>;
|
|
/// }
|
|
#[proc_macro_attribute]
|
|
pub fn wrapper_type(_: TokenStream, input: TokenStream) -> TokenStream {
|
|
let input: syn::ItemTrait = syn::parse(input).unwrap();
|
|
let trait_name = input.ident.clone();
|
|
let type_name = Ident::new(&format!("{}Box", trait_name), trait_name.span());
|
|
|
|
let doc_line = to_doc(&format!("A wrapper type around a generic `{}`", trait_name));
|
|
let tokens = quote! {
|
|
#input
|
|
|
|
#doc_line
|
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
|
#[serde(transparent)]
|
|
pub struct #type_name(serde_json::Map<String, serde_json::Value>);
|
|
|
|
impl #type_name {
|
|
/// Coerce a concrete type into this wrapper type
|
|
///
|
|
/// This is done automatically via TryFrom in proprties setter methods
|
|
pub fn from_concrete<T>(t: T) -> Result<Self, std::io::Error>
|
|
where
|
|
T: #trait_name + serde::ser::Serialize,
|
|
{
|
|
match serde_json::to_value(t)? {
|
|
serde_json::Value::Object(map) => Ok(#type_name(map)),
|
|
_ => Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Not an object")),
|
|
}
|
|
}
|
|
|
|
/// Attempt to deserialize the wrapper type to a concrete type
|
|
///
|
|
/// Before this method is called, the type should be verified via the `kind` or
|
|
/// `is_kind` methods
|
|
pub fn into_concrete<T>(self) -> Result<T, std::io::Error>
|
|
where
|
|
T: #trait_name + serde::de::DeserializeOwned,
|
|
{
|
|
Ok(serde_json::from_value(serde_json::Value::Object(self.0))?)
|
|
}
|
|
|
|
/// Return whether the given wrapper type is expected.
|
|
///
|
|
/// For example
|
|
/// ```ignore
|
|
/// use activitystreams::object::{
|
|
/// kind::ImageType,
|
|
/// apub::Image,
|
|
/// };
|
|
/// if my_wrapper_type.is_kind(ImageType) {
|
|
/// let image = my_wrapper_type.into_concrete::<Image>()?;
|
|
/// ...
|
|
/// }
|
|
/// ```
|
|
pub fn is_kind(&self, kind: impl std::fmt::Display) -> bool {
|
|
self.0["type"] == kind.to_string()
|
|
}
|
|
|
|
/// Return the kind of wrapper type, if present
|
|
///
|
|
/// Example
|
|
/// ```ignore
|
|
/// match my_wrapper_type.kind() {
|
|
/// Some("Image") => {
|
|
/// let image = my_wrapper_type.into_concrete::<Image>()?;
|
|
/// ...
|
|
/// }
|
|
/// _ => ...,
|
|
/// }
|
|
/// ```
|
|
pub fn kind(&self) -> Option<&str> {
|
|
match self.0["type"] {
|
|
serde_json::Value::String(ref s) => Some(s),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
tokens.into()
|
|
}
|
|
|
|
/// Derive implementations Serialize and Deserialize for a constant string Struct type
|
|
///
|
|
/// ```ignore
|
|
/// /// Derive Serialize and Deserialize such that MyStruct becomes the "MyType" string
|
|
/// #[derive(Clone, Debug, UnitString)]
|
|
/// #[unit_string(MyType)]
|
|
/// pub struct MyStruct;
|
|
///
|
|
/// // usage
|
|
/// let _: HashMap<String, MyStruct> = serde_json::from_str(r#"{"type":"MyType"}"#)?;
|
|
/// ```
|
|
#[proc_macro_derive(UnitString, attributes(unit_string))]
|
|
pub fn unit_string(input: TokenStream) -> TokenStream {
|
|
let input: DeriveInput = syn::parse(input).unwrap();
|
|
|
|
let name = input.ident;
|
|
|
|
let attr = input
|
|
.attrs
|
|
.iter()
|
|
.find(|attribute| {
|
|
attribute
|
|
.path
|
|
.segments
|
|
.last()
|
|
.map(|segment| segment.ident == "unit_string")
|
|
.unwrap_or(false)
|
|
})
|
|
.unwrap()
|
|
.clone();
|
|
|
|
let visitor_name = from_value(attr);
|
|
let value = format!("{}", visitor_name);
|
|
|
|
let serialize = quote! {
|
|
impl ::serde::ser::Serialize for #name {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: ::serde::ser::Serializer,
|
|
{
|
|
serializer.serialize_str(#value)
|
|
}
|
|
}
|
|
};
|
|
|
|
let expecting = quote! {
|
|
fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
|
write!(formatter, "The string '{}'", #value)
|
|
}
|
|
};
|
|
|
|
let visit = quote! {
|
|
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
|
where
|
|
E: ::serde::de::Error,
|
|
{
|
|
if v == #value {
|
|
Ok(#name)
|
|
} else {
|
|
Err(::serde::de::Error::custom("Invalid type"))
|
|
}
|
|
}
|
|
};
|
|
|
|
let visitor = quote! {
|
|
struct #visitor_name;
|
|
|
|
impl<'de> ::serde::de::Visitor<'de> for #visitor_name {
|
|
type Value = #name;
|
|
|
|
#expecting
|
|
|
|
#visit
|
|
}
|
|
};
|
|
|
|
let deserialize = quote! {
|
|
impl<'de> ::serde::de::Deserialize<'de> for #name {
|
|
fn deserialize<D>(deserializer: D) -> Result<#name, D::Error>
|
|
where
|
|
D: ::serde::de::Deserializer<'de>,
|
|
{
|
|
deserializer.deserialize_str(#visitor_name)
|
|
}
|
|
}
|
|
};
|
|
|
|
let display = quote! {
|
|
impl std::fmt::Display for #name {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
write!(f, "{}", #value)
|
|
}
|
|
}
|
|
};
|
|
|
|
let c = quote! {
|
|
#serialize
|
|
#visitor
|
|
#deserialize
|
|
#display
|
|
};
|
|
|
|
c.into()
|
|
}
|
|
|
|
fn from_value(attr: Attribute) -> Ident {
|
|
let group = attr
|
|
.tokens
|
|
.into_iter()
|
|
.filter_map(|token_tree| match token_tree {
|
|
TokenTree::Group(group) => Some(group),
|
|
_ => None,
|
|
})
|
|
.next()
|
|
.unwrap();
|
|
|
|
group
|
|
.stream()
|
|
.into_iter()
|
|
.filter_map(|token_tree| match token_tree {
|
|
TokenTree::Ident(ident) => Some(ident),
|
|
_ => None,
|
|
})
|
|
.next()
|
|
.unwrap()
|
|
}
|
|
|
|
fn to_doc(s: &str) -> proc_macro2::TokenStream {
|
|
format!("/// {}", s).parse().unwrap()
|
|
}
|
|
|
|
fn many_docs(v: &[String]) -> proc_macro2::TokenStream {
|
|
v.iter()
|
|
.map(|d| {
|
|
let d = to_doc(d);
|
|
quote! {
|
|
#d
|
|
}
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
/// Generate structs and enums for activitystreams objects
|
|
///
|
|
/// ```rust
|
|
/// use activitystreams_derive::properties;
|
|
///
|
|
/// #[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
|
|
/// pub struct MyStruct;
|
|
///
|
|
/// properties! {
|
|
/// Hello {
|
|
/// docs [ "Defining the HelloProperties struct" ],
|
|
///
|
|
/// field {
|
|
/// types [ String ],
|
|
/// },
|
|
///
|
|
/// other_field {
|
|
/// docs [
|
|
/// "field documentation",
|
|
/// "is cool",
|
|
/// ],
|
|
/// types [
|
|
/// String,
|
|
/// MyStruct,
|
|
/// ],
|
|
/// functional,
|
|
/// required,
|
|
/// rename("@other_field"),
|
|
/// alias [
|
|
/// "@second_field",
|
|
/// "another_field",
|
|
/// ],
|
|
/// },
|
|
/// }
|
|
/// }
|
|
///
|
|
/// let _ = HelloProperties::default();
|
|
/// ```
|
|
#[proc_macro]
|
|
pub fn properties(tokens: TokenStream) -> TokenStream {
|
|
let Properties { name, docs, fields } = parse_macro_input!(tokens as Properties);
|
|
|
|
let docs: proc_macro2::TokenStream = many_docs(&docs);
|
|
|
|
let name = Ident::new(&format!("{}Properties", name), name.span());
|
|
|
|
let (fields, deps): (Vec<_>, Vec<_>) = fields.iter().filter_map(|field| {
|
|
if field.description.types.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
let fname = field.name.clone();
|
|
let fdocs: proc_macro2::TokenStream = many_docs(&field.description.docs);
|
|
|
|
let (ty, deps) = if field.description.types.len() == 1 {
|
|
let ty = Ident::new(&field.description.types.first().unwrap().to_token_stream().to_string(), fname.span());
|
|
if field.description.functional {
|
|
(ty, None)
|
|
} else {
|
|
let enum_ty = Ident::new(&camelize(&format!("{}_{}_enum", name, fname)), fname.span());
|
|
let doc_lines = many_docs(&[
|
|
format!("Variations for the `{}` field from `{}", fname, name),
|
|
String::new(),
|
|
format!("`{}` isn't functional, meaning it can be represented as either a single `{}` or a vector of `{}`.", fname, ty, ty),
|
|
]);
|
|
let deps = quote! {
|
|
#doc_lines
|
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[serde(untagged)]
|
|
pub enum #enum_ty {
|
|
Array(Vec<#ty>),
|
|
Term(#ty),
|
|
}
|
|
|
|
impl Default for #enum_ty {
|
|
fn default() -> Self {
|
|
#enum_ty::Array(Vec::new())
|
|
}
|
|
}
|
|
|
|
impl From<#ty> for #enum_ty {
|
|
fn from(t: #ty) -> Self {
|
|
#enum_ty::Term(t)
|
|
}
|
|
}
|
|
|
|
impl From<Vec<#ty>> for #enum_ty {
|
|
fn from(v: Vec<#ty>) -> Self {
|
|
#enum_ty::Array(v)
|
|
}
|
|
}
|
|
};
|
|
|
|
(enum_ty, Some(deps))
|
|
}
|
|
} else {
|
|
let ty = Ident::new(&camelize(&format!("{}_{}_enum", name, fname)), fname.span());
|
|
|
|
let v_tokens: proc_macro2::TokenStream = field
|
|
.description
|
|
.types
|
|
.iter()
|
|
.map(|v_ty| {
|
|
quote! {
|
|
#v_ty(#v_ty),
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
let first_type = field.description.types.iter().next().unwrap().clone();
|
|
|
|
let deps = if !field.description.functional {
|
|
let term_ty = Ident::new(&camelize(&format!("{}_{}_term_enum", name, fname)), fname.span());
|
|
|
|
let from_tokens: proc_macro2::TokenStream = field
|
|
.description
|
|
.types
|
|
.iter()
|
|
.map(|v_ty| {
|
|
quote! {
|
|
impl From<#v_ty> for #term_ty {
|
|
fn from(item: #v_ty) -> #term_ty {
|
|
#term_ty::#v_ty(item)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
let term_doc_lines = many_docs(&[
|
|
format!("Terminating variations for the `{}` field from `{}`", fname, name),
|
|
String::new(),
|
|
format!("Since {} can be one of multiple types, this enum represents all possibilities of {}", fname, fname),
|
|
]);
|
|
let doc_lines = many_docs(&[
|
|
format!("Non-Terminating variations for the `{}` field from `{}`", fname, name),
|
|
String::new(),
|
|
format!("`{}` isn't functional, meaning it can be represented as either a single `{}` or a vector of `{}`", fname, term_ty, term_ty),
|
|
]);
|
|
quote! {
|
|
#term_doc_lines
|
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[serde(untagged)]
|
|
pub enum #term_ty {
|
|
#v_tokens
|
|
}
|
|
|
|
#doc_lines
|
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[serde(untagged)]
|
|
pub enum #ty {
|
|
Array(Vec<#term_ty>),
|
|
Term(#term_ty),
|
|
}
|
|
|
|
impl Default for #ty {
|
|
fn default() -> Self {
|
|
#ty::Array(Vec::new())
|
|
}
|
|
}
|
|
|
|
impl From<#term_ty> for #ty {
|
|
fn from(term: #term_ty) -> Self {
|
|
#ty::Term(term)
|
|
}
|
|
}
|
|
|
|
impl From<Vec<#term_ty>> for #ty {
|
|
fn from(v: Vec<#term_ty>) -> Self {
|
|
#ty::Array(v)
|
|
}
|
|
}
|
|
|
|
#from_tokens
|
|
}
|
|
} else {
|
|
let from_tokens: proc_macro2::TokenStream = field
|
|
.description
|
|
.types
|
|
.iter()
|
|
.map(|v_ty| {
|
|
quote! {
|
|
impl From<#v_ty> for #ty {
|
|
fn from(item: #v_ty) -> #ty {
|
|
#ty::#v_ty(item)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
let doc_lines = many_docs(&[
|
|
format!("Variations for the `{}` field from `{}`", fname, name),
|
|
String::new(),
|
|
format!("`{}` isn't functional, meaning it can only be represented as a single `{}`", fname, ty),
|
|
String::new(),
|
|
format!("This enum's variants represent all valid types to construct a `{}`", fname),
|
|
]);
|
|
quote! {
|
|
#doc_lines
|
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[serde(untagged)]
|
|
pub enum #ty {
|
|
#v_tokens
|
|
}
|
|
|
|
impl Default for #ty {
|
|
fn default() -> Self {
|
|
#ty::#first_type(Default::default())
|
|
}
|
|
}
|
|
|
|
#from_tokens
|
|
}
|
|
};
|
|
|
|
(ty, Some(deps))
|
|
};
|
|
|
|
let alias_tokens: proc_macro2::TokenStream = field.description.aliases.iter().map(|alias| quote!{
|
|
#[serde(alias = #alias)]
|
|
}).collect();
|
|
let rename_tokens: proc_macro2::TokenStream = field.description.rename.iter().map(|rename| quote!{
|
|
#[serde(rename = #rename)]
|
|
}).collect();
|
|
|
|
let field_tokens = if field.description.required {
|
|
quote! {
|
|
pub #fname: #ty,
|
|
}
|
|
} else {
|
|
quote! {
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub #fname: Option<#ty>,
|
|
}
|
|
};
|
|
|
|
let field_tokens = quote!{
|
|
#fdocs
|
|
#rename_tokens
|
|
#alias_tokens
|
|
#field_tokens
|
|
};
|
|
|
|
let fns = if field.description.types.len() == 1 {
|
|
let v_ty = field.description.types.first().unwrap().clone();
|
|
|
|
let set_ident =
|
|
Ident::new(&format!("set_{}", fname), fname.span());
|
|
let get_ident =
|
|
Ident::new(&format!("get_{}", fname), fname.span());
|
|
|
|
let enum_ty = Ident::new(&camelize(&format!("{}_{}_enum", name, fname)), fname.span());
|
|
|
|
let set_many_ident =
|
|
Ident::new(&format!("set_many_{}", pluralize(fname.to_string())), fname.span());
|
|
let get_many_ident =
|
|
Ident::new(&format!("get_many_{}", pluralize(fname.to_string())), fname.span());
|
|
let add_ident = Ident::new(&format!("add_{}", fname.to_string()), fname.span());
|
|
|
|
if field.description.required {
|
|
if field.description.functional {
|
|
let doc_line = to_doc(&format!("Set `{}` with a type that can be cnoverted into a `{}`", fname, v_ty.to_token_stream()));
|
|
let set = quote! {
|
|
#doc_line
|
|
pub fn #set_ident<T>(&mut self, item: T) -> Result<&mut Self, <T as std::convert::TryInto<#v_ty>>::Error>
|
|
where
|
|
T: std::convert::TryInto<#v_ty>,
|
|
{
|
|
use std::convert::TryInto;
|
|
self.#fname = item.try_into()?;
|
|
Ok(self)
|
|
}
|
|
};
|
|
|
|
let doc_line = to_doc(&format!("Get the `{}` as `{}`", fname, v_ty.to_token_stream()));
|
|
let get = quote! {
|
|
#doc_line
|
|
pub fn #get_ident(&self) -> &#v_ty {
|
|
&self.#fname
|
|
}
|
|
};
|
|
|
|
quote!{
|
|
#get
|
|
#set
|
|
}
|
|
} else {
|
|
let doc_line = to_doc(&format!("Set `{}` with a type that can be converted into a `{}`", fname, v_ty.to_token_stream()));
|
|
let set = quote! {
|
|
#doc_line
|
|
pub fn #set_ident<T>(&mut self, item: T) -> Result<&mut Self, <T as std::convert::TryInto<#v_ty>>::Error>
|
|
where
|
|
T: std::convert::TryInto<#v_ty>,
|
|
{
|
|
use std::convert::TryInto;
|
|
self.#fname = #enum_ty::Term(item.try_into()?);
|
|
Ok(self)
|
|
}
|
|
};
|
|
|
|
let doc_line = to_doc(&format!("Get the `{}` as `{}`", fname, v_ty.to_token_stream()));
|
|
let get = quote! {
|
|
#doc_line
|
|
///
|
|
/// This returns `None` when there is more than one item
|
|
pub fn #get_ident(&self) -> Option<&#v_ty> {
|
|
match self.#fname {
|
|
#enum_ty::Term(ref term) => Some(term),
|
|
_ => None,
|
|
}
|
|
}
|
|
};
|
|
|
|
let doc_line = to_doc(&format!("Set the `{}` with a vector of types that can be converted into `{}`s", fname, v_ty.to_token_stream()));
|
|
let set_many = quote! {
|
|
#doc_line
|
|
pub fn #set_many_ident<T>(&mut self, item: Vec<T>) -> Result<&mut Self, <T as std::convert::TryInto<#v_ty>>::Error>
|
|
where
|
|
T: std::convert::TryInto<#v_ty>,
|
|
{
|
|
let item: Vec<#v_ty> = item.into_iter().map(std::convert::TryInto::try_into).collect::<Result<Vec<_>, _>>()?;
|
|
self.#fname = #enum_ty::Array(item);
|
|
Ok(self)
|
|
}
|
|
};
|
|
|
|
let doc_line = to_doc(&format!("Add a type that can be converted into a `{}` to the `{}` vec", v_ty.to_token_stream(), fname));
|
|
let add = quote! {
|
|
#doc_line
|
|
pub fn #add_ident<T>(&mut self, item: T) -> Result<&mut Self, <T as std::convert::TryInto<#v_ty>>::Error>
|
|
where
|
|
T: std::convert::TryInto<#v_ty>,
|
|
{
|
|
let item = item.try_into()?;
|
|
|
|
let new_vec = match self.#fname {
|
|
#enum_ty::Array(items) => {
|
|
let mut new_vec = Vec::new();
|
|
new_vec.extend(&items)
|
|
new_vec.push(item);
|
|
new_vec
|
|
}
|
|
#enum_ty::Term(old_item) => {
|
|
let mut new_vec = Vec::new();
|
|
new_vec.push(old_item.clone());
|
|
new_vec.push(item);
|
|
new_vec
|
|
}
|
|
};
|
|
|
|
self.#fname = #enum_ty::Array(new_vec);
|
|
Ok(self)
|
|
}
|
|
};
|
|
|
|
let doc_line = to_doc(&format!("Get the `{}` as a slice of `{}`", fname, v_ty.to_token_stream()));
|
|
let get_many = quote! {
|
|
#doc_line
|
|
///
|
|
/// This returns `None` if
|
|
/// - There is only one element
|
|
pub fn #get_many_ident(&self) -> Option<&[#v_ty]> {
|
|
match self.#fname {
|
|
#enum_ty::Array(ref array) => Some(array),
|
|
_ => None,
|
|
}
|
|
}
|
|
};
|
|
|
|
quote! {
|
|
#get
|
|
#set
|
|
#get_many
|
|
#set_many
|
|
#add
|
|
}
|
|
}
|
|
} else if field.description.functional {
|
|
let doc_line = to_doc(&format!("Set the `{}` with a type that can be converted into `{}`", fname, v_ty.to_token_stream()));
|
|
let set = quote! {
|
|
#doc_line
|
|
pub fn #set_ident<T>(&mut self, item: T) -> Result<&mut Self, <T as std::convert::TryInto<#v_ty>>::Error>
|
|
where
|
|
T: std::convert::TryInto<#v_ty>,
|
|
{
|
|
use std::convert::TryInto;
|
|
self.#fname = Some(item.try_into()?);
|
|
Ok(self)
|
|
}
|
|
};
|
|
|
|
let doc_line = to_doc(&format!("Get `{}` as a `{}`", fname, v_ty.to_token_stream()));
|
|
let get = quote! {
|
|
#doc_line
|
|
///
|
|
/// This returns `None` if there is no value present
|
|
pub fn #get_ident(&self) -> Option<&#v_ty> {
|
|
self.#fname.as_ref()
|
|
}
|
|
};
|
|
|
|
quote!{
|
|
#get
|
|
#set
|
|
}
|
|
} else {
|
|
let doc_line = to_doc(&format!("Set the `{}` with a type that can be converted into `{}`", fname, v_ty.to_token_stream()));
|
|
let set = quote! {
|
|
#doc_line
|
|
pub fn #set_ident<T>(&mut self, item: T) -> Result<&mut Self, <T as std::convert::TryInto<#v_ty>>::Error>
|
|
where
|
|
T: std::convert::TryInto<#v_ty>,
|
|
{
|
|
use std::convert::TryInto;
|
|
self.#fname = Some(#enum_ty::Term(item.try_into()?));
|
|
Ok(self)
|
|
}
|
|
};
|
|
|
|
let doc_line = to_doc(&format!("Get `{}` as a `{}`", fname, v_ty.to_token_stream()));
|
|
let get = quote! {
|
|
#doc_line
|
|
///
|
|
/// This returns `None` if
|
|
/// - There is no value present
|
|
/// - There is more than one value present
|
|
pub fn #get_ident(&self) -> Option<&#v_ty> {
|
|
match self.#fname {
|
|
Some(#enum_ty::Term(ref term)) => Some(term),
|
|
_ => None,
|
|
}
|
|
}
|
|
};
|
|
|
|
let doc_line = to_doc(&format!("Set the `{}` with a vector of types that can be converted into `{}`s", fname, v_ty.to_token_stream()));
|
|
let set_many = quote! {
|
|
#doc_line
|
|
pub fn #set_many_ident<T>(&mut self, item: Vec<T>) -> Result<&mut Self, <T as std::convert::TryInto<#v_ty>>::Error>
|
|
where
|
|
T: std::convert::TryInto<#v_ty>,
|
|
{
|
|
let item: Vec<#v_ty> = item.into_iter().map(std::convert::TryInto::try_into).collect::<Result<Vec<_>, _>>()?;
|
|
self.#fname = Some(#enum_ty::Array(item));
|
|
Ok(self)
|
|
}
|
|
};
|
|
|
|
let doc_line = to_doc(&format!("Add a type that can be converted into a `{}` to the `{}` vec", v_ty.to_token_stream(), fname));
|
|
let add = quote! {
|
|
#doc_line
|
|
pub fn #add_ident<T>(&mut self, item: T) -> Result<&mut Self, <T as std::convert::TryInto<#v_ty>>::Error>
|
|
where
|
|
T: std::convert::TryInto<#v_ty>,
|
|
{
|
|
let item = item.try_into()?;
|
|
|
|
let new_vec = match self.#fname.take() {
|
|
Some(#enum_ty::Array(mut items)) => {
|
|
items.push(item);
|
|
items
|
|
}
|
|
Some(#enum_ty::Term(old_item)) => {
|
|
let mut new_vec = Vec::new();
|
|
new_vec.push(old_item.clone());
|
|
new_vec.push(item);
|
|
new_vec
|
|
}
|
|
None => {
|
|
let mut new_vec = Vec::new();
|
|
new_vec.push(item);
|
|
new_vec
|
|
}
|
|
};
|
|
|
|
self.#fname = Some(#enum_ty::Array(new_vec));
|
|
Ok(self)
|
|
}
|
|
};
|
|
|
|
let doc_line = to_doc(&format!("Get `{}` as a slice of `{}`s", fname, v_ty.to_token_stream()));
|
|
let get_many = quote! {
|
|
#doc_line
|
|
///
|
|
/// This returns `None` if
|
|
/// - There is no value present
|
|
/// - There is only one value present
|
|
pub fn #get_many_ident(&self) -> Option<&[#v_ty]> {
|
|
match self.#fname {
|
|
Some(#enum_ty::Array(ref a)) => Some(a),
|
|
_ => None,
|
|
}
|
|
}
|
|
};
|
|
|
|
quote! {
|
|
#get
|
|
#set
|
|
#get_many
|
|
#set_many
|
|
#add
|
|
}
|
|
}
|
|
} else if field.description.functional {
|
|
let tokens: proc_macro2::TokenStream = field
|
|
.description
|
|
.types
|
|
.iter()
|
|
.map(|v_ty| {
|
|
let set_ident =
|
|
Ident::new(&format!("set_{}_{}", fname, snakize(&v_ty.to_token_stream().to_string())), fname.span());
|
|
let get_ident =
|
|
Ident::new(&format!("get_{}_{}", fname, snakize(&v_ty.to_token_stream().to_string())), fname.span());
|
|
|
|
if field.description.required {
|
|
let doc_line = to_doc(&format!("Set the `{}` with a type that can be converted into `{}`", fname, v_ty.to_token_stream()));
|
|
let set = quote! {
|
|
#doc_line
|
|
pub fn #set_ident<T>(&mut self, item: T) -> Result<&mut Self, <T as std::convert::TryInto<#v_ty>>::Error>
|
|
where
|
|
T: std::convert::TryInto<#v_ty>,
|
|
{
|
|
use std::convert::TryInto;
|
|
let item: #v_ty = item.try_into()?;
|
|
self.#fname = item.into();
|
|
Ok(self)
|
|
}
|
|
};
|
|
|
|
let doc_line = to_doc(&format!("Get `{}` as a slice of `{}`s", fname, v_ty.to_token_stream()));
|
|
let get = quote! {
|
|
#doc_line
|
|
///
|
|
/// This returns `None` if
|
|
/// - The requested type is not the stored type
|
|
pub fn #get_ident(&self) -> Option<&#v_ty> {
|
|
match self.#fname {
|
|
#ty::#v_ty(ref term) => Some(term),
|
|
_ => None,
|
|
}
|
|
}
|
|
};
|
|
|
|
quote! {
|
|
#get
|
|
#set
|
|
}
|
|
} else {
|
|
let doc_line = to_doc(&format!("Set `{}` with a value that can be converted into `{}`", fname, v_ty.to_token_stream()));
|
|
let set = quote! {
|
|
#doc_line
|
|
pub fn #set_ident<T>(&mut self, item: T) -> Result<&mut Self, <T as std::convert::TryInto<#v_ty>>::Error>
|
|
where
|
|
T: std::convert::TryInto<#v_ty>,
|
|
{
|
|
use std::convert::TryInto;
|
|
let item: #v_ty = item.try_into()?;
|
|
self.#fname = Some(item.into());
|
|
Ok(self)
|
|
}
|
|
};
|
|
|
|
let doc_line = to_doc(&format!("Get `{}` as a `{}`", fname, v_ty.to_token_stream()));
|
|
let get = quote! {
|
|
#doc_line
|
|
///
|
|
/// This returns `None` if
|
|
/// - There is no value present
|
|
/// - The requested type is not the stored type
|
|
pub fn #get_ident(&self) -> Option<&#v_ty> {
|
|
match self.#fname {
|
|
Some(#ty::#v_ty(ref term)) => Some(term),
|
|
_ => None,
|
|
}
|
|
}
|
|
};
|
|
|
|
quote! {
|
|
#get
|
|
#set
|
|
}
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
quote! {
|
|
#tokens
|
|
}
|
|
} else {
|
|
let term_ty = Ident::new(&camelize(&format!("{}_{}_term_enum", name, fname)), fname.span());
|
|
let tokens: proc_macro2::TokenStream = field
|
|
.description
|
|
.types
|
|
.iter()
|
|
.map(|v_ty| {
|
|
let set_ident =
|
|
Ident::new(&format!("set_{}_{}", fname, snakize(&v_ty.to_token_stream().to_string())), fname.span());
|
|
let get_ident =
|
|
Ident::new(&format!("get_{}_{}", fname, snakize(&v_ty.to_token_stream().to_string())), fname.span());
|
|
|
|
let set_many_ident =
|
|
Ident::new(&format!("set_many_{}_{}", fname, pluralize(snakize(&v_ty.to_token_stream().to_string()))), fname.span());
|
|
let get_many_ident =
|
|
Ident::new(&format!("get_many_{}_{}", fname, pluralize(snakize(&v_ty.to_token_stream().to_string()))), fname.span());
|
|
|
|
if field.description.required {
|
|
let doc_line = to_doc(&format!("Set `{}` with a value that can be converted into `{}`", fname, v_ty.to_token_stream()));
|
|
let set = quote! {
|
|
#doc_line
|
|
pub fn #set_ident<T>(&mut self, item: T) -> Result<&mut Self, <T as std::convert::TryInto<#v_ty>>::Error>
|
|
where
|
|
T: std::convert::TryInto<#v_ty>,
|
|
{
|
|
use std::convert::TryInto;
|
|
let item: #v_ty = item.try_into()?;
|
|
let item: #term_ty = item.into();
|
|
self.#fname = item.into();
|
|
Ok(self)
|
|
}
|
|
};
|
|
|
|
let doc_line = to_doc(&format!("Get the `{}` as a `{}`", fname, v_ty.to_token_stream()));
|
|
let get = quote! {
|
|
#doc_line
|
|
///
|
|
/// This returns `None` if
|
|
/// - There is more than one value present
|
|
/// - The requested type is not the stored type
|
|
pub fn #get_ident(&self) -> Option<&#v_ty> {
|
|
match self.#fname {
|
|
#ty::Term(#term_ty::#v_ty(ref term)) => Some(term),
|
|
_ => None,
|
|
}
|
|
}
|
|
};
|
|
|
|
let doc_line = to_doc(&format!("Set `{}` from a vec of items that can be converted into `{}`s", fname, v_ty.to_token_stream()));
|
|
let set_many = quote! {
|
|
#doc_line
|
|
pub fn #set_many_ident<T>(&mut self, item: Vec<T>) -> Result<&mut Self, <T as std::convert::TryInto<#v_ty>>::Error>
|
|
where
|
|
T: std::convert::TryInto<#v_ty>,
|
|
{
|
|
let item: Vec<#v_ty> = item.into_iter().map(std::convert::TryInto::try_into).collect::<Result<Vec<_>, _>>()?;
|
|
let item: Vec<#term_ty> = item.into_iter().map(Into::into).collect();
|
|
self.#fname = item.into();
|
|
Ok(self)
|
|
}
|
|
};
|
|
|
|
let doc_line = to_doc(&format!("Get `{}` as a vec of `&{}`s", fname, v_ty.to_token_stream()));
|
|
let get_many = quote! {
|
|
#doc_line
|
|
///
|
|
/// This returns `None` if
|
|
/// - There is only one value present
|
|
///
|
|
/// The returned vec will be empty if no values match the requested
|
|
/// type, but values are present.
|
|
pub fn #get_many_ident(&self) -> Option<impl Iterator<Item = &#v_ty>> {
|
|
match self.#fname {
|
|
#ty::Array(ref array) => Some(array.iter().filter_map(|i| match i {
|
|
#term_ty::#v_ty(item) => Some(item),
|
|
_ => None,
|
|
})),
|
|
_ => None,
|
|
}
|
|
}
|
|
};
|
|
|
|
quote! {
|
|
#get
|
|
#set
|
|
#get_many
|
|
#set_many
|
|
}
|
|
} else {
|
|
let doc_line = to_doc(&format!("Set `{}` from a value that can be converted into `{}`", fname, v_ty.to_token_stream()));
|
|
let set = quote! {
|
|
#doc_line
|
|
pub fn #set_ident<T>(&mut self, item: T) -> Result<&mut Self, <T as std::convert::TryInto<#v_ty>>::Error>
|
|
where
|
|
T: std::convert::TryInto<#v_ty>,
|
|
{
|
|
use std::convert::TryInto;
|
|
let item: #v_ty = item.try_into()?;
|
|
let item: #term_ty = item.into();
|
|
self.#fname = Some(item.into());
|
|
Ok(self)
|
|
}
|
|
};
|
|
|
|
let doc_line = to_doc(&format!("Get `{}` as a `{}`", fname, v_ty.to_token_stream()));
|
|
let get = quote! {
|
|
#doc_line
|
|
///
|
|
/// This returns `None` if
|
|
/// - There is no value present
|
|
/// - There is more than one value present
|
|
/// - The requested type is not stored type
|
|
pub fn #get_ident(&self) -> Option<&#v_ty> {
|
|
match self.#fname {
|
|
Some(#ty::Term(#term_ty::#v_ty(ref term))) => Some(term),
|
|
_ => None,
|
|
}
|
|
}
|
|
};
|
|
|
|
let doc_line = to_doc(&format!("Set `{}` from a vec of items that can be converted into `{}`s", fname, v_ty.to_token_stream()));
|
|
let set_many = quote! {
|
|
#doc_line
|
|
pub fn #set_many_ident<T>(&mut self, item: Vec<T>) -> Result<&mut Self, <T as std::convert::TryInto<#v_ty>>::Error>
|
|
where
|
|
T: std::convert::TryInto<#v_ty>,
|
|
{
|
|
let item: Vec<#v_ty> = item.into_iter().map(std::convert::TryInto::try_into).collect::<Result<Vec<_>, _>>()?;
|
|
let item: Vec<#term_ty> = item.into_iter().map(Into::into).collect();
|
|
self.#fname = Some(item.into());
|
|
Ok(self)
|
|
}
|
|
};
|
|
|
|
let doc_line = to_doc(&format!("Get `{}` as a slice of `{}`s", fname, term_ty.to_token_stream()));
|
|
let get_many = quote! {
|
|
#doc_line
|
|
///
|
|
/// This returns `None` if
|
|
/// - There is no value present
|
|
/// - There is only one value present
|
|
pub fn #get_many_ident(&self) -> Option<impl Iterator<Item = &#v_ty>> {
|
|
match self.#fname {
|
|
Some(#ty::Array(ref array)) => Some(array.iter().filter_map(|i| match i {
|
|
#term_ty::#v_ty(item) => Some(item),
|
|
_ => None,
|
|
})),
|
|
_ => None,
|
|
}
|
|
}
|
|
};
|
|
|
|
quote! {
|
|
#get
|
|
#set
|
|
#get_many
|
|
#set_many
|
|
}
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
let delete = if !field.description.required {
|
|
let delete_ident =
|
|
Ident::new(&format!("delete_{}", fname), fname.span());
|
|
|
|
let doc_line = to_doc(&format!("Set the `{}` field to `None`", fname));
|
|
quote! {
|
|
#doc_line
|
|
pub fn #delete_ident(&mut self) -> &mut Self {
|
|
self.#fname = None;
|
|
self
|
|
}
|
|
}
|
|
} else {
|
|
quote! {}
|
|
};
|
|
|
|
quote! {
|
|
#tokens
|
|
|
|
#delete
|
|
}
|
|
};
|
|
|
|
Some(((field_tokens, fns), deps))
|
|
}).unzip();
|
|
|
|
let (field_tokens, fn_tokens): (proc_macro2::TokenStream, proc_macro2::TokenStream) =
|
|
fields.into_iter().unzip();
|
|
let deps_tokens: proc_macro2::TokenStream = deps.into_iter().filter_map(|d| d).collect();
|
|
|
|
let q = quote! {
|
|
#docs
|
|
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct #name {
|
|
#field_tokens
|
|
}
|
|
|
|
impl #name {
|
|
#fn_tokens
|
|
}
|
|
|
|
#deps_tokens
|
|
};
|
|
q.into()
|
|
}
|
|
|
|
mod kw {
|
|
syn::custom_keyword!(types);
|
|
syn::custom_keyword!(functional);
|
|
syn::custom_keyword!(required);
|
|
syn::custom_keyword!(rename);
|
|
syn::custom_keyword!(alias);
|
|
syn::custom_keyword!(docs);
|
|
}
|
|
|
|
struct Properties {
|
|
name: Ident,
|
|
docs: Vec<String>,
|
|
fields: Punctuated<Field, Token![,]>,
|
|
}
|
|
|
|
struct Field {
|
|
name: Ident,
|
|
description: Description,
|
|
}
|
|
|
|
struct Description {
|
|
docs: Vec<String>,
|
|
types: Punctuated<Type, Token![,]>,
|
|
functional: bool,
|
|
required: bool,
|
|
rename: Option<String>,
|
|
aliases: Vec<String>,
|
|
}
|
|
|
|
impl Parse for Properties {
|
|
fn parse(input: ParseStream) -> Result<Self> {
|
|
let name: Ident = input.parse()?;
|
|
|
|
let content;
|
|
let _: token::Brace = braced!(content in input);
|
|
|
|
let docs = parse_string_array::<_, kw::docs>(&&content, kw::docs)?;
|
|
|
|
let fields = Punctuated::<Field, Token![,]>::parse_terminated(&content)?;
|
|
|
|
Ok(Properties { name, docs, fields })
|
|
}
|
|
}
|
|
|
|
impl Parse for Field {
|
|
fn parse(input: ParseStream) -> Result<Self> {
|
|
let name: Ident = input.parse()?;
|
|
|
|
let content;
|
|
let _: token::Brace = braced!(content in input);
|
|
|
|
let description = content.parse()?;
|
|
|
|
Ok(Field { name, description })
|
|
}
|
|
}
|
|
|
|
impl Parse for Description {
|
|
fn parse(input: ParseStream) -> Result<Self> {
|
|
let docs = parse_string_array::<_, kw::docs>(&input, kw::docs)?;
|
|
|
|
let lookahead = input.lookahead1();
|
|
if !lookahead.peek(kw::types) {
|
|
return Err(lookahead.error());
|
|
}
|
|
input.parse::<kw::types>()?;
|
|
|
|
let content;
|
|
let _: token::Bracket = bracketed!(content in input);
|
|
let types = Punctuated::<Type, Token![,]>::parse_terminated(&content)?;
|
|
optional_comma(&input)?;
|
|
|
|
let functional = parse_kw::<_, kw::functional>(&input, kw::functional)?;
|
|
let required = parse_kw::<_, kw::required>(&input, kw::required)?;
|
|
let rename = parse_string_group::<_, kw::rename>(&input, kw::rename)?;
|
|
let aliases = parse_string_array::<_, kw::alias>(&input, kw::alias)?;
|
|
|
|
Ok(Description {
|
|
docs,
|
|
types,
|
|
functional,
|
|
required,
|
|
rename,
|
|
aliases,
|
|
})
|
|
}
|
|
}
|
|
|
|
fn parse_kw<T: Peek + Copy, U: Parse>(input: ParseStream, t: T) -> Result<bool> {
|
|
let lookahead = input.lookahead1();
|
|
if lookahead.peek(t) {
|
|
input.parse::<U>()?;
|
|
optional_comma(&input)?;
|
|
|
|
return Ok(true);
|
|
}
|
|
|
|
Ok(false)
|
|
}
|
|
|
|
fn parse_string_array<T: Peek + Copy, U: Parse>(input: ParseStream, t: T) -> Result<Vec<String>> {
|
|
let lookahead = input.lookahead1();
|
|
if lookahead.peek(t) {
|
|
input.parse::<U>()?;
|
|
let content;
|
|
bracketed!(content in input);
|
|
|
|
let docs = Punctuated::<LitStr, Token![,]>::parse_terminated(&content)?;
|
|
optional_comma(&input)?;
|
|
Ok(docs.into_iter().map(|d| d.value()).collect())
|
|
} else {
|
|
Ok(vec![])
|
|
}
|
|
}
|
|
|
|
fn parse_string_group<T: Peek + Copy, U: Parse>(
|
|
input: ParseStream,
|
|
t: T,
|
|
) -> Result<Option<String>> {
|
|
let lookahead = input.lookahead1();
|
|
if lookahead.peek(t) {
|
|
input.parse::<U>()?;
|
|
let content;
|
|
parenthesized!(content in input);
|
|
let s: LitStr = content.parse()?;
|
|
optional_comma(&input)?;
|
|
|
|
return Ok(Some(s.value()));
|
|
}
|
|
|
|
Ok(None)
|
|
}
|
|
|
|
fn optional_comma(input: ParseStream) -> Result<()> {
|
|
let lookahead = input.lookahead1();
|
|
if lookahead.peek(Token![,]) {
|
|
input.parse::<Token![,]>()?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn camelize(s: &str) -> String {
|
|
let (s, _) = s
|
|
.chars()
|
|
.fold((String::new(), true), |(mut acc, should_upper), c| {
|
|
if c == '_' {
|
|
(acc, true)
|
|
} else {
|
|
if should_upper {
|
|
acc += &c.to_uppercase().to_string();
|
|
} else {
|
|
acc += &c.to_string();
|
|
}
|
|
|
|
(acc, false)
|
|
}
|
|
});
|
|
|
|
s
|
|
}
|
|
|
|
fn snakize(s: &str) -> String {
|
|
s.chars().fold(String::new(), |mut acc, c| {
|
|
if c.is_uppercase() && !acc.is_empty() {
|
|
acc += "_";
|
|
acc += &c.to_lowercase().to_string();
|
|
} else if c.is_uppercase() {
|
|
acc += &c.to_lowercase().to_string();
|
|
} else {
|
|
acc += &c.to_string();
|
|
}
|
|
acc
|
|
})
|
|
}
|
|
|
|
fn pluralize(s: String) -> String {
|
|
if s.ends_with('x') {
|
|
s + "es"
|
|
} else if s.ends_with('s') {
|
|
s
|
|
} else {
|
|
s + "s"
|
|
}
|
|
}
|