Project restructuring, dedicated traits, TryScribeString derive
parent
6a2ffc19eb
commit
0c4c252be9
@ -1,19 +1,7 @@
|
||||
[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"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0"
|
||||
syn = "1.0"
|
||||
quote = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
[workspace]
|
||||
members = [
|
||||
"enumscribe",
|
||||
"enumscribe_derive",
|
||||
"enumscribe_tests",
|
||||
"enumscribe_examples"
|
||||
]
|
||||
|
||||
@ -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