Skip to content
This repository was archived by the owner on Oct 23, 2025. It is now read-only.

Commit 3a5b3a8

Browse files
authored
Merge pull request killercup#15 from spacekookie/master
Adding custom derives for `Render`
2 parents abbe3f1 + fd3fc58 commit 3a5b3a8

File tree

6 files changed

+223
-1
lines changed

6 files changed

+223
-1
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ matrix:
99
- rust: 1.29.1
1010
env: CLIPPY=YESPLEASE
1111
before_script: rustup component add clippy-preview
12-
script: cargo clippy --all-targets -- -D warnings
12+
script: cargo clippy --all-targets --all -- -D warnings
1313
- rust: 1.29.1
1414
env: RUSTFMT=YESPLEASE
1515
before_script: rustup component add rustfmt-preview

Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ license = "Apache-2.0 OR MIT"
77
readme = "README.md"
88
repository = "https://github.com/killercup/output-rs"
99

10+
[workspace]
11+
members = [
12+
"output_derive"
13+
]
14+
1015
[dependencies]
1116
termcolor = "1.0.4"
1217
serde = "1.0.79"
@@ -19,3 +24,4 @@ serde_derive = "1.0.79"
1924
proptest = "0.8.7"
2025
assert_fs = "0.9.0"
2126
predicates = "0.9.0"
27+
output_derive = { version = "0.1", path = "output_derive" }

examples/derive.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
extern crate failure;
2+
extern crate output;
3+
#[macro_use]
4+
extern crate output_derive;
5+
#[macro_use]
6+
extern crate serde_derive;
7+
8+
use output::{human, json};
9+
10+
fn main() -> Result<(), failure::Error> {
11+
let mut out = output::new()
12+
.add_target(json::file("target/foo.log")?)
13+
.add_target(human::stdout()?);
14+
15+
#[derive(Serialize, RenderOutput)]
16+
struct ErrorMessage {
17+
code: i32,
18+
name: String,
19+
message: String,
20+
}
21+
22+
out.print(&ErrorMessage {
23+
code: 42,
24+
name: String::from("info"),
25+
message: String::from("Derive works"),
26+
})?;
27+
28+
out.print(&ErrorMessage {
29+
code: 0,
30+
name: String::from("okay"),
31+
message: String::from("Thanks for stopping by"),
32+
})?;
33+
34+
Ok(())
35+
}

output_derive/Cargo.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "output_derive"
3+
version = "0.1.0"
4+
authors = ["Katharina Fey <[email protected]>"]
5+
6+
[lib]
7+
proc-macro = true
8+
9+
[dependencies]
10+
syn = "0.15"
11+
quote = "0.6"
12+
13+
[dev-dependencies]
14+
output = { version = "0.1", path = ".." }
15+
serde = "1.0.79"
16+
serde_derive = "1.0.79"

output_derive/src/lib.rs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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+
}

output_derive/tests/structs.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
extern crate output;
2+
#[macro_use]
3+
extern crate output_derive;
4+
#[macro_use]
5+
extern crate serde_derive;
6+
7+
#[test]
8+
fn struct_with_named_fields_of_primitive_types() -> Result<(), output::Error> {
9+
#[derive(Serialize, RenderOutput)]
10+
struct ErrorMessage {
11+
code: i32,
12+
name: String,
13+
message: String,
14+
}
15+
16+
let human = output::human::test();
17+
let json = output::json::test();
18+
let mut out = output::new()
19+
.add_target(human.target())
20+
.add_target(json.target());
21+
22+
out.print(&ErrorMessage {
23+
code: 42,
24+
name: String::from("info"),
25+
message: String::from("Derive works"),
26+
})?;
27+
28+
assert_eq!(
29+
human.to_string(),
30+
"code: 42\n\
31+
name: info\n\
32+
message: Derive works\n\n"
33+
);
34+
35+
assert_eq!(
36+
json.to_string(),
37+
"{\"code\":42,\"name\":\"info\",\"message\":\"Derive works\"}\n\n"
38+
);
39+
40+
Ok(())
41+
}
42+
43+
#[test]
44+
fn tuple_struct_of_primitive_types() -> Result<(), output::Error> {
45+
#[derive(Serialize, RenderOutput)]
46+
struct ErrorMessage(i32, String);
47+
48+
let human = output::human::test();
49+
let json = output::json::test();
50+
let mut out = output::new()
51+
.add_target(human.target())
52+
.add_target(json.target());
53+
54+
out.print(&ErrorMessage(42, String::from("Derive works")))?;
55+
56+
assert_eq!(human.to_string(), "(42, Derive works)\n");
57+
58+
assert_eq!(json.to_string(), "[42,\"Derive works\"]\n\n");
59+
60+
Ok(())
61+
}

0 commit comments

Comments
 (0)