deltoid_derive/
lib.rs

1extern crate proc_macro;
2
3#[macro_use] mod error;
4mod gen;
5
6use crate::error::{DeriveError, DeriveResult};
7use crate::gen::InputType;
8use proc_macro::TokenStream;
9use proc_macro2::{TokenStream as TokenStream2};
10#[cfg(feature = "dump-expansions--unstable")]
11use proc_macro2::{Ident as Ident2};
12use quote::quote;
13#[cfg(feature = "dump-expansions--unstable")]
14use std::fs::{remove_file, File, OpenOptions};
15#[cfg(feature = "dump-expansions--unstable")]
16use std::io::Write;
17#[cfg(feature = "dump-expansions--unstable")]
18use std::path::{Path, PathBuf};
19use syn::{parse_macro_input, DeriveInput};
20
21
22#[proc_macro_derive(Delta, attributes(delta))]
23pub fn derive(input: TokenStream) -> TokenStream {
24    let input = parse_macro_input!(input as DeriveInput);
25    let output: TokenStream2 = derive_internal(input).unwrap(
26        // This is a HACK that allows more ergonomic code for the meat of
27        // the macro while still conforming to the required macro signature.
28    );
29    TokenStream::from(output)
30}
31
32#[allow(non_snake_case)]
33fn derive_internal(input: DeriveInput) -> DeriveResult<TokenStream2> {
34    let input_type: InputType = InputType::parse(&input)?;
35    let delta_type_definition = input_type.define_delta_type()?;
36    let impl_Debug            = input_type.define_Debug_impl()?;
37    let impl_Core             = input_type.define_Core_impl()?;
38    let impl_Apply            = input_type.define_Apply_impl()?;
39    let impl_Delta            = input_type.define_Delta_impl()?;
40    let impl_FromDelta        = input_type.define_FromDelta_impl()?;
41    let impl_IntoDelta        = input_type.define_IntoDelta_impl()?;
42    let output: TokenStream2 = quote! {
43        #delta_type_definition
44        #impl_Debug
45        #impl_Core
46        #impl_Apply
47        #impl_Delta
48        #impl_FromDelta
49        #impl_IntoDelta
50    };
51
52    #[cfg(feature = "print-expansions--unstable")]
53    print_generated_code(
54        &delta_type_definition,
55        &impl_Debug,
56        &impl_Core,
57        &impl_Apply,
58        &impl_Delta,
59        &impl_FromDelta,
60        &impl_IntoDelta,
61    );
62
63    #[cfg(feature = "dump-expansions--unstable")]
64    write_generated_code_to_file(
65        input_type.type_name()?,
66        &delta_type_definition,
67        &impl_Debug,
68        &impl_Core,
69        &impl_Apply,
70        &impl_Delta,
71        &impl_FromDelta,
72        &impl_IntoDelta,
73    );
74
75    Ok(output)
76}
77
78#[cfg(feature = "print-expansions--unstable")]
79#[allow(unused, non_snake_case)]
80fn print_generated_code(
81    delta_type_definition: &TokenStream2,
82    impl_Debug: &TokenStream2,
83    impl_Core: &TokenStream2,
84    impl_Apply: &TokenStream2,
85    impl_Delta: &TokenStream2,
86    impl_FromDelta: &TokenStream2,
87    impl_IntoDelta: &TokenStream2,
88) {
89    println!("{}\n", delta_type_definition);
90    println!("{}\n", impl_Debug);
91    println!("{}\n", impl_Core);
92    println!("{}\n", impl_Apply);
93    println!("{}\n", impl_Delta);
94    println!("{}\n", impl_FromDelta);
95    println!("{}\n", impl_IntoDelta);
96    println!("\n\n\n\n");
97}
98
99#[cfg(feature = "dump-expansions--unstable")]
100#[allow(unused, non_snake_case)]
101fn write_generated_code_to_file(
102    type_name: &Ident2,
103    delta_type_definition: &TokenStream2,
104    impl_Debug: &TokenStream2,
105    impl_Core: &TokenStream2,
106    impl_Apply: &TokenStream2,
107    impl_Delta: &TokenStream2,
108    impl_FromDelta: &TokenStream2,
109    impl_IntoDelta: &TokenStream2,
110) {
111    let manifest_dir: &Path = Path::new(env!("CARGO_MANIFEST_DIR"));
112    let expanded_dir: PathBuf = manifest_dir.join("expanded");
113    let filename: PathBuf = expanded_dir.join(&format!("{}.rs", type_name));
114
115    create_dir(&expanded_dir);
116    let _ = remove_file(&filename);
117    let mut file: File = open_file(&filename);
118    println!("wrote {}", filename.display());
119
120    file.write_all(format!("{}", delta_type_definition).as_bytes())
121        .expect("Failed to write delta_type_definition");
122    file.write_all("\n\n".as_bytes()).expect("Failed to write newlines");
123
124    file.write_all(format!("{}", impl_Debug).as_bytes())
125        .expect("Failed to write impl_Debug");
126    file.write_all("\n\n".as_bytes()).expect("Failed to write newlines");
127
128    file.write_all(format!("{}", impl_Core).as_bytes())
129        .expect("Failed to write impl_Core");
130    file.write_all("\n\n".as_bytes()).expect("Failed to write newlines");
131
132    file.write_all(format!("{}", impl_Apply).as_bytes())
133        .expect("Failed to write impl_Apply");
134    file.write_all("\n\n".as_bytes()).expect("Failed to write newlines");
135
136    file.write_all(format!("{}", impl_Delta).as_bytes())
137        .expect("Failed to write impl_Delta");
138    file.write_all("\n\n".as_bytes()).expect("Failed to write newlines");
139
140    file.write_all(format!("{}", impl_FromDelta).as_bytes())
141        .expect("Failed to write impl_FromDelta");
142    file.write_all("\n\n".as_bytes()).expect("Failed to write newlines");
143
144    file.write_all(format!("{}", impl_IntoDelta).as_bytes())
145        .expect("Failed to write impl_IntoDelta");
146    file.write_all("\n\n".as_bytes()).expect("Failed to write newlines");
147
148    file.flush().expect(&format!("Failed to flush {}", filename.display()));
149    std::process::Command::new("rustfmt")
150        .args(&[
151            "--emit", "files", "--edition", "2018",
152            &format!("{}", filename.display())
153        ])
154        .output()
155        .expect("failed to execute rustfmt;");
156}
157
158#[cfg(feature = "dump-expansions--unstable")]
159fn create_dir<P: AsRef<Path>>(path: P) {
160    let path = path.as_ref();
161    std::fs::DirBuilder::new()
162        .recursive(true)
163        .create(path)
164        .expect(&format!("Failed to create dir {}", path.display()));
165}
166
167#[cfg(feature = "dump-expansions--unstable")]
168fn open_file<P: AsRef<Path>>(path: P) -> File {
169    let path = path.as_ref();
170    OpenOptions::new()
171        .create_new(true)
172        .write(true)
173        .truncate(true)
174        .open(path)
175        .expect(&format!("Failed to open {}", path.display()))
176}