Expand custom derives
diff --git a/.travis.yml b/.travis.yml
index 061ab83..73ac924 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -20,14 +20,14 @@
         build)
           cargo build --no-default-features &&
           cargo build &&
-          cargo build --features full &&
-          cargo doc --features full
+          cargo build --features 'full expand' &&
+          cargo doc --features 'full expand'
           ;;
         test)
-          cargo test --features 'full aster visit'
+          cargo test --features 'full aster expand visit'
           ;;
         clippy)
-          cargo build --features 'full aster visit clippy'
+          cargo build --features 'full aster expand visit clippy'
           ;;
       esac
 
diff --git a/Cargo.toml b/Cargo.toml
index 9fe2f0b..f22d2a1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,6 +11,7 @@
 [features]
 default = ["parsing", "printing"]
 aster = []
+expand = ["full", "parsing", "printing"]
 full = []
 parsing = ["unicode-xid"]
 printing = ["quote"]
@@ -24,5 +25,6 @@
 [dev-dependencies]
 syntex_pos = "0.45.0"
 syntex_syntax = "0.45.0"
+tempdir = "0.3.5"
 time = "0.1.35"
 walkdir = "0.1.8"
diff --git a/src/item.rs b/src/item.rs
index be58fbb..cc5b7c3 100644
--- a/src/item.rs
+++ b/src/item.rs
@@ -80,6 +80,20 @@
     Mac(Mac),
 }
 
+impl From<MacroInput> for Item {
+    fn from(input: MacroInput) -> Item {
+        Item {
+            ident: input.ident,
+            vis: input.vis,
+            attrs: input.attrs,
+            node: match input.body {
+                Body::Enum(variants) => ItemKind::Enum(variants, input.generics),
+                Body::Struct(variant_data) => ItemKind::Struct(variant_data, input.generics),
+            },
+        }
+    }
+}
+
 #[derive(Debug, Clone, Eq, PartialEq)]
 pub enum ViewPath {
     /// `foo::bar::baz as quux`
@@ -270,6 +284,8 @@
         item_mac
     ));
 
+    named!(pub items -> Vec<Item>, many0!(item));
+
     named!(item_mac -> Item, do_parse!(
         attrs: many0!(outer_attr) >>
         path: ident >>
@@ -489,7 +505,7 @@
         items: alt!(
             punct!(";") => { |_| None }
             |
-            delimited!(punct!("{"), many0!(item), punct!("}")) => { Some }
+            delimited!(punct!("{"), items, punct!("}")) => { Some }
         ) >>
         (Item {
             ident: id,
diff --git a/src/krate.rs b/src/krate.rs
index 4827088..e8ded24 100644
--- a/src/krate.rs
+++ b/src/krate.rs
@@ -11,11 +11,11 @@
 pub mod parsing {
     use super::*;
     use attr::parsing::inner_attr;
-    use item::parsing::item;
+    use item::parsing::items;
 
     named!(pub krate -> Crate, do_parse!(
         attrs: many0!(inner_attr) >>
-        items: many0!(item) >>
+        items: items >>
         (Crate {
             shebang: None,
             attrs: attrs,
diff --git a/src/lib.rs b/src/lib.rs
index 603e094..fd85720 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -2,6 +2,7 @@
 #![cfg_attr(feature = "clippy", plugin(clippy))]
 
 #[cfg(feature = "printing")]
+#[macro_use]
 extern crate quote;
 
 #[cfg(feature = "parsing")]
@@ -68,6 +69,11 @@
 mod op;
 pub use op::{BinOp, UnOp};
 
+#[cfg(feature = "expand")]
+mod registry;
+#[cfg(feature = "expand")]
+pub use registry::{CustomDerive, Expanded, Registry};
+
 #[cfg(feature = "parsing")]
 mod space;
 
@@ -109,6 +115,11 @@
     }
 
     #[cfg(feature = "full")]
+    pub fn parse_items(input: &str) -> Result<Vec<Item>, String> {
+        unwrap("items", item::parsing::items, input)
+    }
+
+    #[cfg(feature = "full")]
     pub fn parse_expr(input: &str) -> Result<Expr, String> {
         unwrap("expression", expr::parsing::expr, input)
     }
diff --git a/src/registry.rs b/src/registry.rs
new file mode 100644
index 0000000..8a6ce7b
--- /dev/null
+++ b/src/registry.rs
@@ -0,0 +1,335 @@
+use super::{Attribute, AttrStyle, Body, Crate, Ident, Item, ItemKind, MacroInput, MetaItem};
+
+use std::collections::BTreeMap as Map;
+use std::fs::File;
+use std::io::{Read, Write};
+use std::path::Path;
+
+/// Implementation of a custom derive. Custom derives take a struct or enum and
+/// expand it into zero or more items, typically `impl` items.
+pub trait CustomDerive {
+    /// Expand the given struct or enum. If this custom derive modifies the
+    /// input item or preserves it unmodified, it must be returned back in the
+    /// `original` field of Expanded. The custom derive may discard the input
+    /// item by setting `original` to None.
+    fn expand(&self, input: MacroInput) -> Result<Expanded, String>;
+}
+
+/// Produced by expanding a custom derive.
+pub struct Expanded {
+    /// The items (typically `impl` items) constructed by the custom derive.
+    pub new_items: Vec<Item>,
+    /// The input to the custom derive, whether modified or unmodified. If the
+    /// custom derive discards the input item it may do so by setting `original`
+    /// to None.
+    pub original: Option<MacroInput>,
+}
+
+/// Registry of custom derives. Callers add custom derives to a registry, then
+/// use the registry to expand those derives in a source file.
+#[derive(Default)]
+pub struct Registry<'a> {
+    derives: Map<String, Box<CustomDerive + 'a>>,
+}
+
+impl<T> CustomDerive for T
+    where T: Fn(MacroInput) -> Result<Expanded, String>
+{
+    fn expand(&self, input: MacroInput) -> Result<Expanded, String> {
+        self(input)
+    }
+}
+
+impl<'a> Registry<'a> {
+    pub fn new() -> Self {
+        Default::default()
+    }
+
+    /// Register a custom derive. A `fn(MacroInput) -> Result<Expanded, String>`
+    /// may be used as a custom derive.
+    ///
+    /// ```ignore
+    /// registry.add_derive("Serialize", expand_serialize);
+    /// ```
+    pub fn add_derive<T>(&mut self, name: &str, derive: T)
+        where T: CustomDerive + 'a
+    {
+        self.derives.insert(name.into(), Box::new(derive));
+    }
+
+    /// Read Rust source code from the `src` file, expand the custom derives
+    /// that have been registered, and write the result to the `dst` file.
+    pub fn expand_file<S, D>(&self, src: S, dst: D) -> Result<(), String>
+        where S: AsRef<Path>,
+              D: AsRef<Path>
+    {
+        // Open the src file
+        let mut src = match File::open(src) {
+            Ok(open) => open,
+            Err(err) => return Err(err.to_string()),
+        };
+
+        // Read the contents of the src file to a String
+        let mut content = String::new();
+        if let Err(err) = src.read_to_string(&mut content) {
+            return Err(err.to_string());
+        }
+
+        // Parse the contents
+        let krate = try!(super::parse_crate(&content));
+
+        // Expand
+        let expanded = try!(expand_crate(self, krate));
+
+        // Print the expanded code to a String
+        let out = quote!(#expanded).to_string();
+
+        // Create or truncate the dst file, opening in write-only mode
+        let mut dst = match File::create(dst) {
+            Ok(create) => create,
+            Err(err) => return Err(err.to_string()),
+        };
+
+        // Write expanded code to the dst file
+        if let Err(err) = dst.write_all(out.as_bytes()) {
+            return Err(err.to_string());
+        }
+
+        Ok(())
+    }
+}
+
+fn expand_crate(reg: &Registry, krate: Crate) -> Result<Crate, String> {
+    let mut items = Vec::new();
+    for item in krate.items {
+        try!(expand_item(reg, item, Vec::new(), &mut items));
+    }
+    Ok(Crate { items: items, ..krate })
+}
+
+fn expand_item(reg: &Registry, mut item: Item, cfg: Vec<MetaItem>, out: &mut Vec<Item>) -> Result<(), String> {
+    let (body, generics) = match item.node {
+        ItemKind::Enum(variants, generics) => (Body::Enum(variants), generics),
+        ItemKind::Struct(variant_data, generics) => (Body::Struct(variant_data), generics),
+        _ => {
+            // Custom derives cannot apply to this item, preserve it unmodified
+            item.attrs.extend(combine_cfgs(cfg));
+            out.push(item);
+            return Ok(());
+        }
+    };
+    let macro_input = MacroInput {
+        ident: item.ident,
+        vis: item.vis,
+        attrs: item.attrs,
+        generics: generics,
+        body: body,
+    };
+    expand_macro_input(reg, macro_input, cfg, out)
+}
+
+fn expand_macro_input(reg: &Registry,
+                      mut input: MacroInput,
+                      inherited_cfg: Vec<MetaItem>,
+                      out: &mut Vec<Item>)
+                      -> Result<(), String> {
+    let mut derives = Vec::new();
+    let mut all_cfg = inherited_cfg;
+
+    // Find custom derives on this item, removing them from the input
+    input.attrs = input.attrs
+        .into_iter()
+        .flat_map(|attr| {
+            let (new_derives, cfg, attr) = parse_attr(reg, attr);
+            derives.extend(new_derives);
+            all_cfg.extend(cfg);
+            attr
+        })
+        .collect();
+
+    // Expand each custom derive
+    for derive in derives {
+        let expanded = try!(reg.derives[derive.name.as_ref()].expand(input));
+
+        for new_item in expanded.new_items {
+            let mut extended_cfg = all_cfg.clone();
+            extended_cfg.extend(derive.cfg.clone());
+            try!(expand_item(reg, new_item, extended_cfg, out));
+        }
+
+        input = match expanded.original {
+            Some(input) => input,
+            None => return Ok(()),
+        };
+    }
+
+    input.attrs.extend(combine_cfgs(all_cfg));
+    out.push(input.into());
+    Ok(())
+}
+
+struct Derive {
+    name: Ident,
+    /// If the custom derive was behind a cfg_attr
+    cfg: Option<MetaItem>,
+}
+
+/// Pull custom derives and cfgs out of the given Attribute.
+fn parse_attr(reg: &Registry, attr: Attribute) -> (Vec<Derive>, Vec<MetaItem>, Option<Attribute>) {
+    if attr.style != AttrStyle::Outer || attr.is_sugared_doc {
+        return (Vec::new(), Vec::new(), Some(attr));
+    }
+
+    let (name, nested) = match attr.value {
+        MetaItem::List(name, nested) => (name, nested),
+        _ => return (Vec::new(), Vec::new(), Some(attr)),
+    };
+
+    match name.as_ref() {
+        "derive" => {
+            let (derives, attr) = parse_derive_attr(reg, nested);
+            let derives = derives.into_iter()
+                .map(|d| {
+                    Derive {
+                        name: d,
+                        cfg: None,
+                    }
+                })
+                .collect();
+            (derives, Vec::new(), attr)
+        }
+        "cfg_attr" => {
+            let (derives, attr) = parse_cfg_attr(reg, nested);
+            (derives, Vec::new(), attr)
+        }
+        "cfg" => (Vec::new(), nested, None),
+        _ => {
+            // Rebuild the original attribute because it was destructured above
+            let attr = Attribute {
+                style: AttrStyle::Outer,
+                value: MetaItem::List(name, nested),
+                is_sugared_doc: false,
+            };
+            (Vec::new(), Vec::new(), Some(attr))
+        }
+    }
+}
+
+/// Assuming the given nested meta-items came from a #[derive(...)] attribute,
+/// pull out the ones that are custom derives.
+fn parse_derive_attr(reg: &Registry, nested: Vec<MetaItem>) -> (Vec<Ident>, Option<Attribute>) {
+    let mut derives = Vec::new();
+
+    let remaining: Vec<_> = nested.into_iter()
+        .flat_map(|meta| {
+            let word = match meta {
+                MetaItem::Word(word) => word,
+                _ => return Some(meta),
+            };
+            if reg.derives.contains_key(word.as_ref()) {
+                derives.push(word);
+                None
+            } else {
+                Some(MetaItem::Word(word))
+            }
+        })
+        .collect();
+
+    let attr = if remaining.is_empty() {
+        // Elide an empty #[derive()]
+        None
+    } else {
+        Some(Attribute {
+            style: AttrStyle::Outer,
+            value: MetaItem::List("derive".into(), remaining),
+            is_sugared_doc: false,
+        })
+    };
+
+    (derives, attr)
+}
+
+/// Assuming the given nested meta-items came from a #[cfg_attr(...)] attribute,
+/// pull out any custom derives contained within.
+fn parse_cfg_attr(reg: &Registry, nested: Vec<MetaItem>) -> (Vec<Derive>, Option<Attribute>) {
+    if nested.len() != 2 {
+        let attr = Attribute {
+            style: AttrStyle::Outer,
+            value: MetaItem::List("cfg_attr".into(), nested),
+            is_sugared_doc: false,
+        };
+        return (Vec::new(), Some(attr));
+    }
+
+    let mut iter = nested.into_iter();
+    let cfg = iter.next().unwrap();
+    let arg = iter.next().unwrap();
+
+    let (name, nested) = match arg {
+        MetaItem::List(name, nested) => (name, nested),
+        _ => {
+            let attr = Attribute {
+                style: AttrStyle::Outer,
+                value: MetaItem::List("cfg_attr".into(), vec![cfg, arg]),
+                is_sugared_doc: false,
+            };
+            return (Vec::new(), Some(attr));
+        }
+    };
+
+    if name == "derive" {
+        let (derives, attr) = parse_derive_attr(reg, nested);
+        let derives = derives.into_iter()
+            .map(|d| {
+                Derive {
+                    name: d,
+                    cfg: Some(cfg.clone()),
+                }
+            })
+            .collect();
+        let attr = attr.map(|attr| {
+            Attribute {
+                style: AttrStyle::Outer,
+                value: MetaItem::List("cfg_attr".into(), vec![cfg, attr.value]),
+                is_sugared_doc: false,
+            }
+        });
+        (derives, attr)
+    } else {
+        let attr = Attribute {
+            style: AttrStyle::Outer,
+            value: MetaItem::List("cfg_attr".into(), vec![cfg, MetaItem::List(name, nested)]),
+            is_sugared_doc: false,
+        };
+        (Vec::new(), Some(attr))
+    }
+}
+
+/// Combine a list of cfg expressions into an attribute like `#[cfg(a)]` or
+/// `#[cfg(all(a, b, c))]`, or nothing if there are no cfg expressions.
+fn combine_cfgs(cfg: Vec<MetaItem>) -> Option<Attribute> {
+    // Flatten `all` cfgs so we don't nest `all` inside of `all`.
+    let cfg: Vec<_> = cfg.into_iter().flat_map(|cfg| {
+        let (name, nested) = match cfg {
+            MetaItem::List(name, nested) => (name, nested),
+            _ => return vec![cfg],
+        };
+        if name == "all" {
+            nested
+        } else {
+            vec![MetaItem::List(name, nested)]
+        }
+    }).collect();
+
+    let value = match cfg.len() {
+        0 => return None,
+        1 => cfg,
+        _ => vec![MetaItem::List("all".into(), cfg)],
+    };
+
+    Some(Attribute {
+        style: AttrStyle::Outer,
+        value: MetaItem::List("cfg".into(), value),
+        is_sugared_doc: false,
+    })
+}
diff --git a/tests/test_expand.rs b/tests/test_expand.rs
new file mode 100644
index 0000000..33effd5
--- /dev/null
+++ b/tests/test_expand.rs
@@ -0,0 +1,137 @@
+#![cfg(feature = "expand")]
+
+extern crate syn;
+use syn::*;
+
+#[macro_use]
+extern crate quote;
+use quote::Tokens;
+
+extern crate tempdir;
+use tempdir::TempDir;
+
+use std::fs::File;
+use std::io::{Read, Write};
+
+#[test]
+fn test_cfg() {
+    let original = quote! {
+        use super::*;
+
+        #[derive(A)]
+        struct P;
+
+        #[cfg_attr(feature = "q", derive(A))]
+        struct Q;
+
+        #[derive(A)]
+        #[cfg(feature = "r")]
+        struct R;
+
+        #[cfg(feature = "s1")]
+        #[cfg(all(feature = "s2", feature = "s3"))]
+        #[cfg_attr(feature = "s4", derive(A))]
+        struct S;
+    };
+
+    let expected = quote! {
+        // Unmodified from the input
+        use super::*;
+
+        type P = ();
+
+        #[cfg(feature = "q")]
+        type Q = ();
+
+        #[cfg(feature = "r")]
+        type R = ();
+
+        #[cfg(all(feature = "s1", feature = "s2", feature = "s3", feature = "s4"))]
+        type S = ();
+    };
+
+    test_expand(original, expected);
+}
+
+#[test]
+fn test_recursive() {
+    let original = quote! {
+        #[d]
+        #[cfg_attr(feature = "f", derive(Copy, B, Clone))]
+        #[e]
+        #[cfg(feature = "e")]
+        struct T;
+    };
+
+    let expected = quote! {
+        // From #[derive(A)] on struct S produced by #[derive(B)]
+        #[cfg(all(feature = "e", feature = "f", feature = "g"))]
+        type S = ();
+
+        // From #[derive(B)] on struct T
+        #[cfg(all(feature = "e", feature = "f"))]
+        impl B for T {}
+
+        // From the input
+        #[d]
+        #[cfg_attr(feature = "f", derive(Copy, Clone))]
+        #[e]
+        #[cfg(feature = "e")]
+        struct T;
+    };
+
+    test_expand(original, expected);
+}
+
+fn test_expand(original: Tokens, expected: Tokens) {
+    let dir = TempDir::new("syn").expect("create temp dir");
+    let src_path = dir.path().join("expand.in.rs");
+    let dst_path = dir.path().join("expand.rs");
+
+    // Write the src file
+    let mut src_file = File::create(&src_path).expect("create temp file");
+    src_file.write_all(original.to_string().as_bytes()).expect("write temp file");
+
+    // Run expansion
+    let mut registry = Registry::new();
+    registry.add_derive("A", expand_a);
+    registry.add_derive("B", expand_b);
+    registry.expand_file(&src_path, &dst_path).unwrap();
+
+    // Read the dst file
+    let mut expanded = String::new();
+    let mut dst_file = File::open(&dst_path).expect("open output file");
+    dst_file.read_to_string(&mut expanded).expect("read output file");
+
+    assert_eq!(expanded, expected.to_string());
+}
+
+fn expand_a(input: MacroInput) -> Result<Expanded, String> {
+    let name = &input.ident;
+    let out = quote! {
+        type #name = ();
+    };
+    Ok(Expanded {
+        new_items: parse_items(&out.to_string()).unwrap(),
+        original: None,
+    })
+}
+
+fn expand_b(input: MacroInput) -> Result<Expanded, String> {
+    assert_eq!(quote!(#input), quote! {
+        #[d]
+        #[cfg_attr(feature = "f", derive(Copy, Clone))]
+        #[e]
+        struct T;
+    });
+    let out = quote! {
+        #[cfg_attr(feature = "g", derive(A))]
+        struct S;
+
+        impl B for T {}
+    };
+    Ok(Expanded {
+        new_items: parse_items(&out.to_string()).unwrap(),
+        original: Some(input),
+    })
+}