Project restructuring, dedicated traits, TryScribeString derive
parent
6a2ffc19eb
commit
0c4c252be9
@ -1,19 +1,7 @@
|
|||||||
[package]
|
[workspace]
|
||||||
name = "enumscribe"
|
members = [
|
||||||
version = "0.1.0"
|
"enumscribe",
|
||||||
authors = ["Tom Panton <pantonshire@gmail.com>"]
|
"enumscribe_derive",
|
||||||
edition = "2018"
|
"enumscribe_tests",
|
||||||
license = "MIT"
|
"enumscribe_examples"
|
||||||
description = "Procedural macros for converting between enums and strings"
|
]
|
||||||
|
|
||||||
[lib]
|
|
||||||
proc-macro = true
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
proc-macro2 = "1.0"
|
|
||||||
syn = "1.0"
|
|
||||||
quote = "1.0"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
serde = "1.0"
|
|
||||||
serde_json = "1.0"
|
|
||||||
|
|||||||
@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "enumscribe"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Tom Panton <pantonshire@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
license = "MIT"
|
||||||
|
description = "Procedural macros for converting between enums and strings"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
enumscribe_derive = { path = "../enumscribe_derive" }
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
#[macro_use]
|
||||||
|
extern crate enumscribe_derive;
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
pub use enumscribe_derive::*;
|
||||||
|
|
||||||
|
//TODO
|
||||||
|
pub trait ScribeStaticStr {
|
||||||
|
fn scribe(&self) -> &'static str;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO
|
||||||
|
pub trait TryScribeStaticStr {
|
||||||
|
fn try_scribe(&self) -> Option<&'static str>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ScribeString {
|
||||||
|
fn scribe(&self) -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait TryScribeString {
|
||||||
|
fn try_scribe(&self) -> Option<String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO
|
||||||
|
pub trait ScribeCowStr {
|
||||||
|
fn scribe(&self) -> Cow<'static, str>;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO
|
||||||
|
pub trait TryScribeCowStr {
|
||||||
|
fn try_scribe(&self) -> Option<Cow<'static, str>>;
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
[package]
|
||||||
|
name = "enumscribe_derive"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Tom Panton <pantonshire@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
license = "MIT"
|
||||||
|
description = "Procedural macros for converting between enums and strings"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
proc-macro2 = "1.0"
|
||||||
|
syn = "1.0"
|
||||||
|
quote = "1.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
serde = "1.0"
|
||||||
|
serde_json = "1.0"
|
||||||
@ -0,0 +1,138 @@
|
|||||||
|
use proc_macro::TokenStream;
|
||||||
|
use std::iter;
|
||||||
|
|
||||||
|
use quote::quote;
|
||||||
|
use syn::{Data, DataEnum, DeriveInput};
|
||||||
|
|
||||||
|
use error::{MacroError, MacroResult};
|
||||||
|
|
||||||
|
mod enums;
|
||||||
|
mod attribute;
|
||||||
|
mod error;
|
||||||
|
|
||||||
|
const CRATE_ATTR: &'static str = "enumscribe";
|
||||||
|
|
||||||
|
const NAME: &'static str = "str";
|
||||||
|
const OTHER: &'static str = "other";
|
||||||
|
const IGNORE: &'static str = "ignore";
|
||||||
|
const CASE_INSENSITIVE: &'static str = "case_insensitive";
|
||||||
|
|
||||||
|
macro_rules! proc_try {
|
||||||
|
($x:expr) => {
|
||||||
|
match $x {
|
||||||
|
Ok(val) => val,
|
||||||
|
Err(err) => return err.into()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro_derive(ScribeString, attributes(enumscribe))]
|
||||||
|
pub fn derive_enum_to_string(input: TokenStream) -> TokenStream {
|
||||||
|
let input: DeriveInput = syn::parse(input)
|
||||||
|
.expect("failed to parse input");
|
||||||
|
|
||||||
|
let enum_data = proc_try!(get_enum_data(&input));
|
||||||
|
let parsed_enum = proc_try!(enums::parse_enum(enum_data));
|
||||||
|
|
||||||
|
let enum_ident = &input.ident;
|
||||||
|
let enum_idents = iter::repeat(enum_ident);
|
||||||
|
|
||||||
|
let mut match_patterns = Vec::with_capacity(parsed_enum.variants.len());
|
||||||
|
let mut match_results = Vec::with_capacity(parsed_enum.variants.len());
|
||||||
|
|
||||||
|
for variant in parsed_enum.variants.iter() {
|
||||||
|
match variant.match_variant(
|
||||||
|
|name| Ok(quote! { <_ as ::std::borrow::ToOwned>::to_owned(#name) }),
|
||||||
|
|field| Ok(quote! { <_ as ::std::convert::Into<::std::string::String>>::into(#field) }),
|
||||||
|
) {
|
||||||
|
Ok(Some((pattern, result))) => {
|
||||||
|
match_patterns.push(pattern);
|
||||||
|
match_results.push(result);
|
||||||
|
},
|
||||||
|
|
||||||
|
Ok(None) => return MacroError::new(format!(
|
||||||
|
"cannot derive ScribeString for {} because the variant {} is marked as {}\n\
|
||||||
|
explanation: since {} is ignored, it cannot be guaranteed that the enum can \
|
||||||
|
always be successfully converted to a String\n\
|
||||||
|
hint: try deriving TryScribeString instead",
|
||||||
|
enum_ident.to_string(), variant.data.ident.to_string(), IGNORE,
|
||||||
|
variant.data.ident.to_string(),
|
||||||
|
), variant.span).into(),
|
||||||
|
|
||||||
|
Err(err) => return err.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(quote! {
|
||||||
|
impl ::enumscribe::ScribeString for #enum_ident {
|
||||||
|
fn scribe(&self) -> ::std::string::String {
|
||||||
|
match self {
|
||||||
|
#(#enum_idents::#match_patterns => #match_results,)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro_derive(TryScribeString, attributes(enumscribe))]
|
||||||
|
pub fn derive_try_enum_to_string(input: TokenStream) -> TokenStream {
|
||||||
|
let input: DeriveInput = syn::parse(input)
|
||||||
|
.expect("failed to parse input");
|
||||||
|
|
||||||
|
let enum_data = proc_try!(get_enum_data(&input));
|
||||||
|
let parsed_enum = proc_try!(enums::parse_enum(enum_data));
|
||||||
|
|
||||||
|
let enum_ident = &input.ident;
|
||||||
|
let enum_idents = iter::repeat(enum_ident);
|
||||||
|
|
||||||
|
let mut ignore_variant = false;
|
||||||
|
let mut match_patterns = Vec::with_capacity(parsed_enum.variants.len());
|
||||||
|
let mut match_results = Vec::with_capacity(parsed_enum.variants.len());
|
||||||
|
|
||||||
|
for variant in parsed_enum.variants.iter() {
|
||||||
|
match variant.match_variant(
|
||||||
|
|name| Ok(quote! { ::std::option::Option::Some(<_ as ::std::borrow::ToOwned>::to_owned(#name)) }),
|
||||||
|
|field| Ok(quote! { ::std::option::Option::Some(<_ as ::std::convert::Into<::std::string::String>>::into(#field)) }),
|
||||||
|
) {
|
||||||
|
Ok(Some((pattern, result))) => {
|
||||||
|
match_patterns.push(pattern);
|
||||||
|
match_results.push(result);
|
||||||
|
},
|
||||||
|
|
||||||
|
Ok(None) => ignore_variant = true,
|
||||||
|
|
||||||
|
Err(err) => return err.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let ignore_arm = if ignore_variant {
|
||||||
|
quote! { _ => ::std::option::Option::None, }
|
||||||
|
} else {
|
||||||
|
quote! {}
|
||||||
|
};
|
||||||
|
|
||||||
|
(quote! {
|
||||||
|
impl ::enumscribe::TryScribeString for #enum_ident {
|
||||||
|
fn try_scribe(&self) -> ::std::option::Option<::std::string::String> {
|
||||||
|
match self {
|
||||||
|
#(#enum_idents::#match_patterns => #match_results,)*
|
||||||
|
#ignore_arm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_enum_data(input: &DeriveInput) -> MacroResult<&DataEnum> {
|
||||||
|
let enum_data = match &input.data {
|
||||||
|
Data::Enum(enum_data) => enum_data,
|
||||||
|
Data::Struct(_) => return Err(MacroError::new("enumscribe cannot be used for structs", input.ident.span())),
|
||||||
|
Data::Union(_) => return Err(MacroError::new("enumscribe cannot be used for unions", input.ident.span()))
|
||||||
|
};
|
||||||
|
|
||||||
|
if enum_data.variants.is_empty() {
|
||||||
|
return Err(MacroError::new("enumscribe cannot be used for empty enums", input.ident.span()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(enum_data)
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "enumscribe_examples"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Tom Panton <pantonshire@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
enumscribe = { path = "../enumscribe" }
|
||||||
|
enumscribe_derive = { path = "../enumscribe_derive" }
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
println!("Hello, world!");
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
[package]
|
||||||
|
name = "enumscribe_tests"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Tom Panton <pantonshire@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
println!("Hello, world!");
|
||||||
|
}
|
||||||
@ -1,87 +0,0 @@
|
|||||||
use proc_macro::TokenStream;
|
|
||||||
use std::iter;
|
|
||||||
|
|
||||||
use quote::quote;
|
|
||||||
use syn::{Data, DataEnum, DeriveInput};
|
|
||||||
|
|
||||||
use error::{MacroError, MacroResult};
|
|
||||||
|
|
||||||
use crate::enums::VariantType;
|
|
||||||
|
|
||||||
mod enums;
|
|
||||||
mod attribute;
|
|
||||||
mod error;
|
|
||||||
|
|
||||||
const CRATE_ATTR: &'static str = "enumscribe";
|
|
||||||
|
|
||||||
const NAME: &'static str = "str";
|
|
||||||
const OTHER: &'static str = "other";
|
|
||||||
const IGNORE: &'static str = "ignore";
|
|
||||||
const CASE_INSENSITIVE: &'static str = "case_insensitive";
|
|
||||||
|
|
||||||
macro_rules! proc_try {
|
|
||||||
($x:expr) => {
|
|
||||||
match $x {
|
|
||||||
Ok(val) => val,
|
|
||||||
Err(err) => return err.into()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[proc_macro_derive(EnumToString, attributes(enumscribe))]
|
|
||||||
pub fn derive_enum_to_string(input: TokenStream) -> TokenStream {
|
|
||||||
let input: DeriveInput = syn::parse(input)
|
|
||||||
.expect("failed to parse input");
|
|
||||||
|
|
||||||
let enum_data = proc_try!(get_enum_data(&input));
|
|
||||||
let parsed_enum = proc_try!(enums::parse_enum(enum_data));
|
|
||||||
|
|
||||||
let enum_ident = &input.ident;
|
|
||||||
let enum_idents = iter::repeat(enum_ident);
|
|
||||||
|
|
||||||
if parsed_enum.variants.is_empty() {
|
|
||||||
return MacroError::new(format!(
|
|
||||||
"cannot derive EnumToString for {} because it has no variants",
|
|
||||||
enum_ident.to_string()
|
|
||||||
), input.ident.span()).into();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut match_patterns = Vec::with_capacity(parsed_enum.variants.len());
|
|
||||||
let mut match_results = Vec::with_capacity(parsed_enum.variants.len());
|
|
||||||
|
|
||||||
for variant in parsed_enum.variants.iter() {
|
|
||||||
if let Some((pattern, result)) = variant.match_variant(
|
|
||||||
|name| quote! { <_ as ::std::borrow::ToOwned>::to_owned(#name) },
|
|
||||||
|field| quote! { <_ as ::std::convert::Into<::std::string::String>>::into(#field) }
|
|
||||||
) {
|
|
||||||
match_patterns.push(pattern);
|
|
||||||
match_results.push(result);
|
|
||||||
} else {
|
|
||||||
return MacroError::new(format!(
|
|
||||||
"cannot derive EnumToString for {} because the variant {} is marked as {}\n\
|
|
||||||
explanation: since {} is ignored, it cannot be guaranteed that the enum can \
|
|
||||||
always be successfully converted to a String", //TODO: suggest another derive to use instead
|
|
||||||
enum_ident.to_string(), variant.data.ident.to_string(), IGNORE,
|
|
||||||
variant.data.ident.to_string(),
|
|
||||||
), variant.span).into();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(quote! {
|
|
||||||
impl ::std::convert::From<#enum_ident> for ::std::string::String {
|
|
||||||
fn from(__enum_to_scribe: #enum_ident) -> Self {
|
|
||||||
match __enum_to_scribe {
|
|
||||||
#(#enum_idents::#match_patterns => #match_results),*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_enum_data(input: &DeriveInput) -> MacroResult<&DataEnum> {
|
|
||||||
match &input.data {
|
|
||||||
Data::Enum(data) => Ok(data),
|
|
||||||
Data::Struct(_) => Err(MacroError::new("enumscribe cannot be used for structs", input.ident.span())),
|
|
||||||
Data::Union(_) => Err(MacroError::new("enumscribe cannot be used for unions", input.ident.span()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue