|
| 1 | +//! Custom derives for `output.rs` |
| 2 | +//! |
| 3 | +//! # Examples |
| 4 | +//! |
| 5 | +//! ```rust |
| 6 | +//! extern crate output; |
| 7 | +//! #[macro_use] extern crate output_derive; |
| 8 | +//! #[macro_use] extern crate serde_derive; |
| 9 | +//! |
| 10 | +//! #[derive(Serialize, RenderOutput)] |
| 11 | +//! struct Message { |
| 12 | +//! code: i32, |
| 13 | +//! message: String, |
| 14 | +//! } |
| 15 | +//! |
| 16 | +//! # fn main() -> Result<(), output::Error> { |
| 17 | +//! # use output::human; |
| 18 | +//! # let test_target = human::test(); |
| 19 | +//! let mut out = output::new().add_target(test_target.target()); |
| 20 | +//! out.print(&Message { |
| 21 | +//! code: 42, |
| 22 | +//! message: String::from("Derive works"), |
| 23 | +//! })?; |
| 24 | +//! # assert_eq!(test_target.to_string(), "code: 42\nmessage: Derive works\n\n"); |
| 25 | +//! # Ok(()) } |
| 26 | +//! ``` |
| 27 | +
|
| 28 | +#![recursion_limit = "1024"] |
| 29 | + |
| 30 | +extern crate proc_macro; |
| 31 | +#[macro_use] |
| 32 | +extern crate quote; |
| 33 | +#[macro_use] |
| 34 | +extern crate syn; |
| 35 | + |
| 36 | +use proc_macro::TokenStream; |
| 37 | +use syn::{Data, DeriveInput, Fields}; |
| 38 | + |
| 39 | +#[proc_macro_derive(RenderOutput)] |
| 40 | +pub fn render_output(input: TokenStream) -> TokenStream { |
| 41 | + let ast: DeriveInput = parse_macro_input!(input as DeriveInput); |
| 42 | + |
| 43 | + let name = &ast.ident; |
| 44 | + let render_span = match ast.data { |
| 45 | + Data::Struct(s) => match s.fields { |
| 46 | + Fields::Named(..) => { |
| 47 | + let fields = s.fields.iter().map(|f| { |
| 48 | + let x = f.ident.clone().unwrap(); |
| 49 | + quote!(#x) |
| 50 | + }); |
| 51 | + let names = s |
| 52 | + .fields |
| 53 | + .iter() |
| 54 | + .map(|f| f.ident.clone().unwrap().to_string()); |
| 55 | + quote! { |
| 56 | + let mut span = output::components::span(); |
| 57 | + #( |
| 58 | + span = span.add_item(#names); |
| 59 | + span = span.add_item(": "); |
| 60 | + span = span.add_item(output::components::text(&self.#fields.to_string())); |
| 61 | + span = span.add_item("\n"); |
| 62 | + )* |
| 63 | + span.render_for_humans(fmt)?; |
| 64 | + } |
| 65 | + } |
| 66 | + Fields::Unnamed(..) => { |
| 67 | + let field_count = s.fields.iter().count(); |
| 68 | + let fields = (0..field_count) |
| 69 | + .fold(Vec::new(), |mut res, i| { |
| 70 | + res.push(quote! { span = span.add_item(output::components::text(&self.#i.to_string())); }); |
| 71 | + if i < field_count - 1 { |
| 72 | + res.push(quote! { span = span.add_item(", "); }); |
| 73 | + } |
| 74 | + res |
| 75 | + }); |
| 76 | + |
| 77 | + quote! { |
| 78 | + let mut span = output::components::span(); |
| 79 | + span = span.add_item("("); |
| 80 | + #(#fields)* |
| 81 | + span = span.add_item(")"); |
| 82 | + span.render_for_humans(fmt)?; |
| 83 | + } |
| 84 | + } |
| 85 | + _ => panic!("Unit structs not supported for now, sorry."), |
| 86 | + }, |
| 87 | + _ => panic!("Only structs supported for now, sorry."), |
| 88 | + }; |
| 89 | + let exp = quote! { |
| 90 | + impl output::Render for #name { |
| 91 | + fn render_for_humans(&self, fmt: &mut output::human::Formatter) -> Result<(), output::Error> { |
| 92 | + #render_span |
| 93 | + Ok(()) |
| 94 | + } |
| 95 | + |
| 96 | + fn render_json(&self, fmt: &mut output::json::Formatter) -> Result<(), output::Error> { |
| 97 | + fmt.write(self)?; |
| 98 | + Ok(()) |
| 99 | + } |
| 100 | + } |
| 101 | + }; |
| 102 | + |
| 103 | + TokenStream::from(exp) |
| 104 | +} |
0 commit comments