mirror of
https://github.com/danog/strum.git
synced 2024-11-26 12:04:38 +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