mirror of
https://github.com/danog/strum.git
synced 2024-11-26 20:14:40 +01:00
Implemented initial version of macros and code
This commit is contained in:
commit
87a88dc92c
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
target
|
||||||
|
Cargo.lock
|
6
strum/Cargo.toml
Normal file
6
strum/Cargo.toml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[package]
|
||||||
|
name = "strum"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Peter Glotfelty <glotfelty.2@osu.edu>"]
|
||||||
|
|
||||||
|
[dependencies]
|
15
strum/src/lib.rs
Normal file
15
strum/src/lib.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
pub enum ParseError {
|
||||||
|
VariantNotFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait IntoEnumIterator {
|
||||||
|
type Iterator;
|
||||||
|
|
||||||
|
fn iter() -> Self::Iterator;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn it_works() {}
|
||||||
|
}
|
12
strum_macros/Cargo.toml
Normal file
12
strum_macros/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "strum_macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Peter Glotfelty <peglotfe@microsoft.com>"]
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
quote = "*"
|
||||||
|
syn = "*"
|
||||||
|
strum = { path = "../strum" }
|
257
strum_macros/src/lib.rs
Normal file
257
strum_macros/src/lib.rs
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
extern crate strum;
|
||||||
|
extern crate syn;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate quote;
|
||||||
|
extern crate proc_macro;
|
||||||
|
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use syn::Attribute;
|
||||||
|
|
||||||
|
#[proc_macro_derive(FromString,attributes(strum))]
|
||||||
|
pub fn from_string(input: TokenStream) -> TokenStream {
|
||||||
|
let s = input.to_string();
|
||||||
|
let ast = syn::parse_derive_input(&s).unwrap();
|
||||||
|
|
||||||
|
let toks = from_string_inner(&ast);
|
||||||
|
toks.parse().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro_derive(EnumIter,attributes(strum))]
|
||||||
|
pub fn unit_enum_iter(input: TokenStream) -> TokenStream {
|
||||||
|
let s = input.to_string();
|
||||||
|
let ast = syn::parse_derive_input(&s).unwrap();
|
||||||
|
|
||||||
|
let toks = enum_iter(&ast);
|
||||||
|
println!("{:?}", toks);
|
||||||
|
toks.parse().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro_derive(EnumHelp,attributes(strum))]
|
||||||
|
pub fn unit_enum_help_messages(input: TokenStream) -> TokenStream {
|
||||||
|
let s = input.to_string();
|
||||||
|
let ast = syn::parse_derive_input(&s).unwrap();
|
||||||
|
|
||||||
|
let toks = enum_help(&ast);
|
||||||
|
toks.parse().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_attrs<'a>(attrs: &'a [Attribute], attr: &str, prop: &str) -> Vec<&'a str> {
|
||||||
|
attrs.iter()
|
||||||
|
// Get all the attributes with our tag on them.
|
||||||
|
.filter_map(|attribute| {
|
||||||
|
use syn::MetaItem::*;
|
||||||
|
if let List(ref i, ref nested) = attribute.value {
|
||||||
|
if i == attr { Some(nested) } else { None }
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.flat_map(|nested| nested)
|
||||||
|
// Get all the inner elements as long as they start with ser.
|
||||||
|
.filter_map(|attribute| {
|
||||||
|
use syn::NestedMetaItem::*;
|
||||||
|
use syn::MetaItem::*;
|
||||||
|
if let &MetaItem(NameValue(ref i, syn::Lit::Str(ref s, ..))) = attribute {
|
||||||
|
if i == prop { Some(&**s) } else { None }
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_disabled(attrs: &[Attribute]) -> bool {
|
||||||
|
let v = extract_attrs(attrs, "strum", "disabled");
|
||||||
|
match v.len() {
|
||||||
|
0 => false,
|
||||||
|
1 => v[0] == "true",
|
||||||
|
_ => panic!("Can't have multiple values for 'disabled'"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_string_inner(ast: &syn::DeriveInput) -> quote::Tokens {
|
||||||
|
let name = &ast.ident;
|
||||||
|
let variants = match ast.body {
|
||||||
|
syn::Body::Enum(ref v) => v,
|
||||||
|
_ => panic!("FromString only works on Enums"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut arms = Vec::new();
|
||||||
|
for variant in variants {
|
||||||
|
use syn::VariantData::*;
|
||||||
|
let ident = &variant.ident;
|
||||||
|
|
||||||
|
// Look at all the serialize attributes.
|
||||||
|
let mut attrs = extract_attrs(&variant.attrs, "strum", "serialize");
|
||||||
|
if is_disabled(&variant.attrs) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we don't have any custom variants, add the default name.
|
||||||
|
if attrs.len() == 0 {
|
||||||
|
attrs.push(ident.as_ref());
|
||||||
|
}
|
||||||
|
|
||||||
|
let params = match variant.data {
|
||||||
|
Unit => quote::Ident::from(""),
|
||||||
|
Tuple(ref fields) => {
|
||||||
|
let default = fields.iter()
|
||||||
|
.map(|_| "Default::default()")
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
|
quote::Ident::from(&*format!("({})", default))
|
||||||
|
}
|
||||||
|
Struct(ref fields) => {
|
||||||
|
let default = fields.iter()
|
||||||
|
.map(|field| {
|
||||||
|
format!("{}:{}", field.ident.as_ref().unwrap(), "Default::default()")
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
|
quote::Ident::from(&*format!("{{{}}}", default))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
arms.push(quote!{ #(#attrs)|* => Ok(#name::#ident #params) });
|
||||||
|
}
|
||||||
|
arms.push(quote! { _ => Err(strum::ParseError::VariantNotFound) });
|
||||||
|
quote!{
|
||||||
|
impl std::str::FromStr for #name {
|
||||||
|
type Err = strum::ParseError;
|
||||||
|
fn from_str(s: &str) -> Result<#name,strum::ParseError> {
|
||||||
|
match s {
|
||||||
|
#(#arms),*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enum_iter(ast: &syn::DeriveInput) -> quote::Tokens {
|
||||||
|
let name = &ast.ident;
|
||||||
|
let variants = match ast.body {
|
||||||
|
syn::Body::Enum(ref v) => v,
|
||||||
|
_ => panic!("EnumIter only works on Enums"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut arms = Vec::new();
|
||||||
|
let enabled = variants.iter().filter(|variant| !is_disabled(&variant.attrs));
|
||||||
|
|
||||||
|
for (idx, variant) in enabled.enumerate() {
|
||||||
|
use syn::VariantData::*;
|
||||||
|
let ident = &variant.ident;
|
||||||
|
let params = match variant.data {
|
||||||
|
Unit => quote::Ident::from(""),
|
||||||
|
Tuple(ref fields) => {
|
||||||
|
let default = fields.iter()
|
||||||
|
.map(|_| "Default::default()")
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
|
quote::Ident::from(&*format!("({})", default))
|
||||||
|
}
|
||||||
|
Struct(ref fields) => {
|
||||||
|
let default = fields.iter()
|
||||||
|
.map(|field| {
|
||||||
|
format!("{}:{}", field.ident.as_ref().unwrap(), "Default::default()")
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
|
quote::Ident::from(&*format!("{{{}}}", default))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
arms.push(quote!{#idx => Some(#name::#ident #params)});
|
||||||
|
}
|
||||||
|
|
||||||
|
arms.push(quote! { _ => None });
|
||||||
|
let iter_name = quote::Ident::from(&*format!("{}Iter", name));
|
||||||
|
quote!{
|
||||||
|
struct #iter_name {
|
||||||
|
idx: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl strum::IntoEnumIterator for #name {
|
||||||
|
type Iterator = #iter_name;
|
||||||
|
fn iter() -> #iter_name {
|
||||||
|
#iter_name {
|
||||||
|
idx:0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for #iter_name {
|
||||||
|
type Item = #name;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<#name> {
|
||||||
|
use std::default::Default;
|
||||||
|
let output = match self.idx {
|
||||||
|
#(#arms),*
|
||||||
|
};
|
||||||
|
|
||||||
|
self.idx += 1;
|
||||||
|
output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enum_help(ast: &syn::DeriveInput) -> quote::Tokens {
|
||||||
|
let name = &ast.ident;
|
||||||
|
let variants = match ast.body {
|
||||||
|
syn::Body::Enum(ref v) => v,
|
||||||
|
_ => panic!("EnumHelp only works on Enums"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut arms = Vec::new();
|
||||||
|
let mut detailed_arms = Vec::new();
|
||||||
|
let enabled = variants.iter().filter(|variant| !is_disabled(&variant.attrs));
|
||||||
|
for variant in enabled {
|
||||||
|
let mut help = extract_attrs(&variant.attrs, "strum", "help");
|
||||||
|
let ident = &variant.ident;
|
||||||
|
if help.len() > 1 {
|
||||||
|
panic!("More than one help message on {}::{}", name, ident);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(msg) = help.pop() {
|
||||||
|
use syn::VariantData::*;
|
||||||
|
let params = match variant.data {
|
||||||
|
Unit => quote::Ident::from(""),
|
||||||
|
Tuple(..) => quote::Ident::from("(..)"),
|
||||||
|
Struct(..) => quote::Ident::from("{{..}}"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Push the simple message.
|
||||||
|
arms.push(quote!{ &#name::#ident #params => Some(#msg) });
|
||||||
|
|
||||||
|
// Create the more complex message.
|
||||||
|
let mut serialize = extract_attrs(&variant.attrs, "strum", "serialize");
|
||||||
|
if serialize.len() == 0 {
|
||||||
|
serialize.push(ident.as_ref());
|
||||||
|
}
|
||||||
|
|
||||||
|
let detailed_msg = format!("{}: {}", serialize.join(", "), msg);
|
||||||
|
detailed_arms.push(quote!{&#name::#ident #params => Some(#detailed_msg) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
arms.push(quote!{ _ => None });
|
||||||
|
detailed_arms.push(quote!{ _ => None });
|
||||||
|
quote!{
|
||||||
|
impl #name {
|
||||||
|
pub fn get_help(&self) -> Option<&str> {
|
||||||
|
match self {
|
||||||
|
#(#arms),*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_detailed_help(&self) -> Option<&str> {
|
||||||
|
match self {
|
||||||
|
#(#detailed_arms),*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
strum_macros/src/main.rs
Normal file
36
strum_macros/src/main.rs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#[macro_use]
|
||||||
|
extern crate macro_test;
|
||||||
|
extern crate strum;
|
||||||
|
|
||||||
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
|
#[derive(Debug,Eq,PartialEq,FromString,EnumIter,EnumHelp)]
|
||||||
|
enum Color {
|
||||||
|
Red(usize),
|
||||||
|
Blue { hue: usize },
|
||||||
|
#[strum(serialize="y",serialize="yellow",help="This is the color yellow")]
|
||||||
|
Yellow,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(EnumHelp)]
|
||||||
|
enum Gender {
|
||||||
|
#[strum(serialize="-b",serialize="-boy", help="I'm a boy")]
|
||||||
|
Boy(usize),
|
||||||
|
Girl(char),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
println!("Hello world");
|
||||||
|
|
||||||
|
for color in Color::iter() {
|
||||||
|
if let Some(msg) = color.get_help() {
|
||||||
|
println!("{}", msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(msg) = color.get_detailed_help() {
|
||||||
|
println!("{}", msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{}", (Gender::Boy(1)).get_help().unwrap());
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user