├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── README.md ├── custom_debug_derive ├── .gitignore ├── Cargo.toml └── src │ ├── field_attributes.rs │ ├── lib.rs │ ├── result_into_stream_ext.rs │ ├── retain_ext.rs │ └── tests.rs ├── examples ├── buffers.rs ├── conditional_skip.rs └── hex.rs └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ### Added 10 | - Better error messages 11 | - no_std support 12 | - `CustomDebug` alias for `Debug` 13 | 14 | ### Changed 15 | - BREAKING: upgrade from edition 2018 to edition 2021 16 | - BREAKING: internal use of Rust 1.65.0 features (GATs) 17 | 18 | ## [0.5.1] 19 | 20 | ### Fixed 21 | - Typo in the readme (@ComputerDruid) 22 | - Warnings and code formatting 23 | 24 | ## [0.5.0] - 2020-06-12 25 | 26 | ### Changed 27 | - rename CustomDebug to Debug 28 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "custom_debug" 3 | version = "0.6.2" 4 | authors = ["panicbit "] 5 | description = "Derive Debug with a custom format per field" 6 | license = "Apache-2.0 OR MIT" 7 | repository = "https://github.com/panicbit/custom_debug" 8 | readme = "README.md" 9 | edition = "2021" 10 | 11 | [dependencies] 12 | custom_debug_derive = { version = "0.6.2", path = "custom_debug_derive" } 13 | 14 | [workspace] 15 | members = ["custom_debug_derive"] 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # custom_debug 2 | 3 | Derive `Debug` with a custom format per field. 4 | 5 | # Example usage 6 | 7 | Here is a showcase of `custom_debug`s features: 8 | 9 | ```rust 10 | use custom_debug::Debug; 11 | use std::fmt; 12 | 13 | #[derive(Debug)] 14 | struct Foo { 15 | #[debug(format = "{} things")] 16 | x: i32, 17 | #[debug(skip)] 18 | y: i32, 19 | #[debug(with = hex_fmt)] 20 | z: i32, 21 | #[debug(skip_if = Option::is_none)] 22 | label: Option, 23 | } 24 | 25 | fn hex_fmt(n: &T, f: &mut fmt::Formatter) -> fmt::Result { 26 | write!(f, "0x{:02X?}", n) 27 | } 28 | ``` 29 | 30 | The resulting debug output would look something like this: 31 | 32 | ``` 33 | Foo { 34 | x: 42 things, 35 | z: 0xAB 36 | } 37 | ``` 38 | 39 | # Field attributes reference 40 | 41 | Attributes within a section below are considered mutually exclusive. 42 | 43 | ## Skip attributes 44 | 45 | | | | 46 | |-|-| 47 | | `skip` | Unconditionally skips a field. | 48 | | `skip_if = path::to::function` | Skips a field if `path::to::function(&field)` returns `true`. | 49 | 50 | ## Format attributes 51 | 52 | | | | 53 | |-|-| 54 | | `format = "format string {}"` | Formats a field using a format string. Must contain a placeholder (`{}`) with modifiers of your choice. | 55 | | `with = path::to::formatter` | Formats a field using `path::to::formatter`. The required signature is `fn(&T, &mut std::fmt::Formatter) -> std::fmt::Result` where `T` is a type compatible with the field's type (i.e. the function can be generic and coercions apply). | 56 | -------------------------------------------------------------------------------- /custom_debug_derive/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /custom_debug_derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "custom_debug_derive" 3 | version = "0.6.2" 4 | authors = ["panicbit "] 5 | description = "Derive Debug with a custom format per field" 6 | license = "Apache-2.0 OR MIT" 7 | repository = "https://github.com/panicbit/custom_debug" 8 | readme = "../README.md" 9 | edition = "2021" 10 | 11 | [lib] 12 | proc-macro = true 13 | 14 | [dependencies] 15 | synstructure = "0.13.1" 16 | proc-macro2 = "1.0.76" 17 | syn = "2.0.48" 18 | quote = "1.0.35" 19 | darling = "0.20.3" 20 | -------------------------------------------------------------------------------- /custom_debug_derive/src/field_attributes.rs: -------------------------------------------------------------------------------- 1 | use darling::util::Flag; 2 | use darling::FromMeta; 3 | use syn::ExprPath; 4 | 5 | #[derive(Default)] 6 | pub struct FieldAttributes { 7 | pub skip_mode: SkipMode, 8 | pub debug_format: DebugFormat, 9 | } 10 | 11 | impl FieldAttributes { 12 | fn new(internal: InternalFieldAttributes) -> darling::Result { 13 | let mut skip_mode = SkipMode::Default; 14 | let mut debug_format = DebugFormat::Default; 15 | 16 | if internal.skip.is_present() { 17 | skip_mode = skip_mode.try_combine(SkipMode::Always)?; 18 | } 19 | 20 | if let Some(skip_if) = internal.skip_if { 21 | skip_mode = skip_mode.try_combine(SkipMode::Condition(skip_if))?; 22 | } 23 | 24 | if let Some(format) = internal.format { 25 | debug_format = debug_format.try_combine(DebugFormat::Format(format))?; 26 | } 27 | 28 | if let Some(with) = internal.with { 29 | debug_format = debug_format.try_combine(DebugFormat::With(with))?; 30 | } 31 | 32 | Ok(Self { 33 | skip_mode, 34 | debug_format, 35 | }) 36 | } 37 | 38 | pub fn try_combine(self, other: Self) -> darling::Result { 39 | let skip_mode = self.skip_mode.try_combine(other.skip_mode)?; 40 | let debug_format = self.debug_format.try_combine(other.debug_format)?; 41 | 42 | Ok(Self { 43 | skip_mode, 44 | debug_format, 45 | }) 46 | } 47 | } 48 | 49 | impl FromMeta for FieldAttributes { 50 | fn from_nested_meta(item: &darling::ast::NestedMeta) -> darling::Result { 51 | InternalFieldAttributes::from_nested_meta(item).and_then(FieldAttributes::new) 52 | } 53 | 54 | fn from_meta(item: &syn::Meta) -> darling::Result { 55 | InternalFieldAttributes::from_meta(item).and_then(FieldAttributes::new) 56 | } 57 | 58 | fn from_none() -> Option { 59 | InternalFieldAttributes::from_none().and_then(|attrs| FieldAttributes::new(attrs).ok()) 60 | } 61 | 62 | fn from_word() -> darling::Result { 63 | InternalFieldAttributes::from_word().and_then(FieldAttributes::new) 64 | } 65 | 66 | fn from_list(items: &[darling::ast::NestedMeta]) -> darling::Result { 67 | InternalFieldAttributes::from_list(items).and_then(FieldAttributes::new) 68 | } 69 | } 70 | 71 | #[derive(FromMeta, Debug, PartialEq, Eq, Default)] 72 | pub enum DebugFormat { 73 | #[default] 74 | Default, 75 | Format(String), 76 | With(ExprPath), 77 | } 78 | 79 | impl DebugFormat { 80 | fn try_combine(self, other: Self) -> darling::Result { 81 | match (&self, &other) { 82 | (DebugFormat::Default, _) => Ok(other), 83 | (_, DebugFormat::Default) => Ok(self), 84 | _ => Err(conflicting_format_options_error()), 85 | } 86 | } 87 | } 88 | 89 | #[derive(Default, PartialEq, Eq)] 90 | pub enum SkipMode { 91 | #[default] 92 | Default, 93 | Condition(ExprPath), 94 | Always, 95 | } 96 | 97 | impl SkipMode { 98 | fn try_combine(self, other: Self) -> darling::Result { 99 | match (&self, &other) { 100 | (SkipMode::Default, _) => Ok(other), 101 | (_, SkipMode::Default) => Ok(self), 102 | _ => Err(conflicting_skip_options_error()), 103 | } 104 | } 105 | } 106 | 107 | #[derive(FromMeta)] 108 | struct InternalFieldAttributes { 109 | skip: Flag, 110 | skip_if: Option, 111 | format: Option, 112 | with: Option, 113 | } 114 | 115 | fn conflicting_skip_options_error() -> darling::Error { 116 | darling::Error::custom("Conflicting skip options") 117 | } 118 | 119 | fn conflicting_format_options_error() -> darling::Error { 120 | darling::Error::custom("Conflicting format options") 121 | } 122 | -------------------------------------------------------------------------------- /custom_debug_derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | use darling::FromMeta; 2 | use proc_macro2::TokenStream; 3 | use quote::quote; 4 | use syn::{Fields, Result}; 5 | use synstructure::{decl_derive, AddBounds, BindingInfo, Structure, VariantInfo}; 6 | 7 | use crate::field_attributes::{DebugFormat, FieldAttributes, SkipMode}; 8 | use crate::result_into_stream_ext::ResultIntoStreamExt; 9 | use crate::retain_ext::RetainExt; 10 | 11 | mod field_attributes; 12 | mod result_into_stream_ext; 13 | mod retain_ext; 14 | #[cfg(test)] 15 | mod tests; 16 | 17 | decl_derive!([Debug, attributes(debug)] => custom_debug_derive); 18 | 19 | fn custom_debug_derive(mut structure: Structure) -> Result { 20 | filter_out_skipped_fields(&mut structure)?; 21 | 22 | structure.add_bounds(AddBounds::Fields); 23 | 24 | let match_arms = 25 | structure.each_variant(|variant| generate_match_arm_body(variant).into_stream()); 26 | 27 | Ok(structure.gen_impl(quote! { 28 | gen impl ::core::fmt::Debug for @Self { 29 | fn fmt(&self, fmt: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { 30 | match self { 31 | #match_arms 32 | } 33 | } 34 | } 35 | })) 36 | } 37 | 38 | fn filter_out_skipped_fields(structure: &mut Structure) -> Result<()> { 39 | structure.try_retain(|binding| { 40 | let field_attributes = parse_field_attributes(binding)?; 41 | 42 | Ok(field_attributes.skip_mode != SkipMode::Always) 43 | })?; 44 | 45 | Ok(()) 46 | } 47 | 48 | fn generate_match_arm_body(variant: &VariantInfo) -> Result { 49 | let name = variant.ast().ident.to_string(); 50 | let debug_builder = match variant.ast().fields { 51 | Fields::Named(_) | Fields::Unit => quote! { debug_struct }, 52 | Fields::Unnamed(_) => quote! { debug_tuple }, 53 | }; 54 | let mut debug_builder_calls = Vec::new(); 55 | 56 | for binding in variant.bindings() { 57 | let field_attributes = parse_field_attributes(binding)?; 58 | 59 | let debug_builder_call = match &field_attributes.skip_mode { 60 | SkipMode::Default => generate_debug_builder_call(binding, &field_attributes)?, 61 | SkipMode::Condition(condition) => { 62 | let debug_builder_call = generate_debug_builder_call(binding, &field_attributes)?; 63 | 64 | quote! { 65 | if (!#condition(#binding)) { 66 | #debug_builder_call 67 | } 68 | } 69 | } 70 | SkipMode::Always => quote! {}, 71 | }; 72 | 73 | debug_builder_calls.push(debug_builder_call); 74 | } 75 | 76 | Ok(quote! { 77 | let mut debug_builder = fmt.#debug_builder(#name); 78 | 79 | #(#debug_builder_calls)* 80 | 81 | debug_builder.finish() 82 | }) 83 | } 84 | 85 | fn generate_debug_builder_call( 86 | binding: &BindingInfo, 87 | field_attributes: &FieldAttributes, 88 | ) -> Result { 89 | let format = generate_debug_impl(binding, &field_attributes.debug_format); 90 | 91 | let debug_builder_call = 92 | if let Some(ref name) = binding.ast().ident.as_ref().map(<_>::to_string) { 93 | quote! { 94 | debug_builder.field(#name, #format); 95 | } 96 | } else { 97 | quote! { 98 | debug_builder.field(#format); 99 | } 100 | }; 101 | 102 | Ok(debug_builder_call) 103 | } 104 | 105 | fn generate_debug_impl(binding: &BindingInfo, debug_format: &DebugFormat) -> TokenStream { 106 | match debug_format { 107 | DebugFormat::Default => quote! { #binding }, 108 | DebugFormat::Format(format) => quote! { &format_args!(#format, #binding) }, 109 | DebugFormat::With(with) => quote! { 110 | { 111 | struct DebugWith<'a, T: 'a> { 112 | data: &'a T, 113 | fmt: fn(&T, &mut ::core::fmt::Formatter) -> ::core::fmt::Result, 114 | } 115 | 116 | impl<'a, T: 'a> ::core::fmt::Debug for DebugWith<'a, T> { 117 | fn fmt(&self, fmt: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { 118 | (self.fmt)(self.data, fmt) 119 | } 120 | } 121 | 122 | &DebugWith { 123 | data: #binding, 124 | fmt: #with, 125 | } 126 | } 127 | }, 128 | } 129 | } 130 | 131 | fn parse_field_attributes(binding: &BindingInfo<'_>) -> Result { 132 | let mut combined_field_attributes = FieldAttributes::default(); 133 | 134 | for attr in &binding.ast().attrs { 135 | if !attr.path().is_ident("debug") { 136 | continue; 137 | } 138 | 139 | let field_attributes = FieldAttributes::from_meta(&attr.meta)?; 140 | 141 | combined_field_attributes = combined_field_attributes.try_combine(field_attributes)?; 142 | } 143 | 144 | Ok(combined_field_attributes) 145 | } 146 | -------------------------------------------------------------------------------- /custom_debug_derive/src/result_into_stream_ext.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | 3 | pub(crate) trait ResultIntoStreamExt { 4 | fn into_stream(self) -> TokenStream; 5 | } 6 | 7 | impl ResultIntoStreamExt for syn::Result { 8 | fn into_stream(self) -> TokenStream { 9 | match self { 10 | Ok(stream) => stream, 11 | Err(err) => err.into_compile_error(), 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /custom_debug_derive/src/retain_ext.rs: -------------------------------------------------------------------------------- 1 | use synstructure::BindingInfo; 2 | 3 | use synstructure::Structure; 4 | 5 | use syn::Result; 6 | 7 | pub(crate) trait RetainExt { 8 | type Item<'a>; 9 | 10 | fn retain(&mut self, f: F) -> &mut Self 11 | where 12 | F: for<'a> FnMut(Self::Item<'a>) -> bool; 13 | 14 | fn try_retain(&mut self, mut f: F) -> Result<&mut Self> 15 | where 16 | F: for<'a> FnMut(Self::Item<'a>) -> Result, 17 | { 18 | let mut filter_err = None; 19 | 20 | let result = self.retain(|value| { 21 | if filter_err.is_some() { 22 | return false; 23 | } 24 | 25 | f(value).unwrap_or_else(|err| { 26 | filter_err = Some(err); 27 | false 28 | }) 29 | }); 30 | 31 | filter_err.map(Err).unwrap_or(Ok(result)) 32 | } 33 | } 34 | 35 | impl RetainExt for Structure<'_> { 36 | type Item<'a> = &'a BindingInfo<'a>; 37 | 38 | fn retain(&mut self, mut f: F) -> &mut Self 39 | where 40 | F: for<'a> FnMut(&'a BindingInfo<'a>) -> bool, 41 | { 42 | self.filter(|value| f(value)) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /custom_debug_derive/src/tests.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::match_single_binding)] 2 | use super::custom_debug_derive; 3 | use synstructure::test_derive; 4 | 5 | #[test] 6 | fn test_default_struct() { 7 | test_derive! { 8 | custom_debug_derive { 9 | struct Point { 10 | x: f32, 11 | y: f32, 12 | } 13 | } 14 | 15 | expands to { 16 | const _: () = { 17 | impl ::core::fmt::Debug for Point { 18 | fn fmt(&self, fmt: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { 19 | match self { 20 | Point { x: ref __binding_0, y: ref __binding_1, } => { 21 | let mut debug_builder = fmt.debug_struct("Point"); 22 | debug_builder.field("x", __binding_0); 23 | debug_builder.field("y", __binding_1); 24 | debug_builder.finish() 25 | } 26 | } 27 | } 28 | } 29 | }; 30 | } 31 | } 32 | } 33 | 34 | #[test] 35 | fn test_format() { 36 | test_derive! { 37 | custom_debug_derive { 38 | struct Point { 39 | #[debug(format = "{:.02}")] 40 | x: f32, 41 | y: f32, 42 | } 43 | } 44 | 45 | expands to { 46 | const _: () = { 47 | impl ::core::fmt::Debug for Point { 48 | fn fmt(&self, fmt: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { 49 | match self { 50 | Point { x: ref __binding_0, y: ref __binding_1, } => { 51 | let mut debug_builder = fmt.debug_struct("Point"); 52 | debug_builder.field("x", &format_args!("{:.02}", __binding_0)); 53 | debug_builder.field("y", __binding_1); 54 | debug_builder.finish() 55 | } 56 | } 57 | } 58 | } 59 | }; 60 | } 61 | 62 | no_build 63 | } 64 | } 65 | 66 | #[test] 67 | fn test_with() { 68 | test_derive! { 69 | custom_debug_derive { 70 | struct Point { 71 | #[debug(with = "my_fmt")] 72 | x: f32, 73 | y: f32, 74 | } 75 | } 76 | 77 | expands to { 78 | const _: () = { 79 | impl ::core::fmt::Debug for Point { 80 | fn fmt(&self, fmt: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { 81 | match self { 82 | Point { x: ref __binding_0, y: ref __binding_1, } => { 83 | let mut debug_builder = fmt.debug_struct("Point"); 84 | debug_builder.field("x", { 85 | struct DebugWith<'a, T: 'a> { 86 | data: &'a T, 87 | fmt: fn(&T, &mut ::core::fmt::Formatter) -> ::core::fmt::Result, 88 | } 89 | 90 | impl<'a, T: 'a> ::core::fmt::Debug for DebugWith<'a, T> { 91 | fn fmt(&self, fmt: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { 92 | (self.fmt)(self.data, fmt) 93 | } 94 | } 95 | 96 | &DebugWith { 97 | data: __binding_0, 98 | fmt: my_fmt, 99 | } 100 | }); 101 | debug_builder.field("y", __binding_1); 102 | debug_builder.finish() 103 | } 104 | } 105 | } 106 | } 107 | }; 108 | } 109 | 110 | no_build 111 | } 112 | } 113 | 114 | #[test] 115 | fn test_skip() { 116 | test_derive! { 117 | custom_debug_derive { 118 | struct Point { 119 | x: f32, 120 | #[debug(skip)] 121 | y: f32, 122 | z: f32, 123 | } 124 | } 125 | 126 | expands to { 127 | const _: () = { 128 | impl ::core::fmt::Debug for Point { 129 | fn fmt(&self, fmt: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { 130 | match self { 131 | Point { x: ref __binding_0, z: ref __binding_2, .. } => { 132 | let mut debug_builder = fmt.debug_struct("Point"); 133 | debug_builder.field("x", __binding_0); 134 | debug_builder.field("z", __binding_2); 135 | debug_builder.finish() 136 | } 137 | } 138 | } 139 | } 140 | }; 141 | } 142 | 143 | no_build 144 | } 145 | } 146 | 147 | #[test] 148 | fn test_conditional_skip() { 149 | test_derive! { 150 | custom_debug_derive { 151 | struct Point { 152 | x: f32, 153 | #[debug(skip_if = Option::is_none)] 154 | y: Option, 155 | z: f32, 156 | } 157 | } 158 | 159 | expands to { 160 | const _: () = { 161 | impl ::core::fmt::Debug for Point { 162 | fn fmt(&self, fmt: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { 163 | match self { 164 | Point { x: ref __binding_0, y: ref __binding_1, z: ref __binding_2, } => { 165 | let mut debug_builder = fmt.debug_struct("Point"); 166 | debug_builder.field("x", __binding_0); 167 | 168 | if (!Option::is_none(__binding_1)) { 169 | debug_builder.field("y", __binding_1); 170 | } 171 | 172 | debug_builder.field("z", __binding_2); 173 | debug_builder.finish() 174 | } 175 | } 176 | } 177 | } 178 | }; 179 | } 180 | 181 | no_build 182 | } 183 | } 184 | 185 | #[test] 186 | fn test_enum() { 187 | test_derive! { 188 | custom_debug_derive { 189 | enum Foo { 190 | Bar(#[debug(format = "{}i32")] i32, String), 191 | Quux { x: f32, y: f32 }, 192 | } 193 | } 194 | 195 | expands to { 196 | const _: () = { 197 | impl ::core::fmt::Debug for Foo { 198 | fn fmt(&self, fmt: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { 199 | match self { 200 | Foo::Bar(ref __binding_0, ref __binding_1,) => { 201 | let mut debug_builder = fmt.debug_tuple("Bar"); 202 | debug_builder.field(&format_args!("{}i32", __binding_0)); 203 | debug_builder.field(__binding_1); 204 | debug_builder.finish() 205 | } 206 | Foo::Quux { x: ref __binding_0, y: ref __binding_1, } => { 207 | let mut debug_builder = fmt.debug_struct("Quux"); 208 | debug_builder.field("x", __binding_0); 209 | debug_builder.field("y", __binding_1); 210 | debug_builder.finish() 211 | } 212 | } 213 | } 214 | } 215 | }; 216 | } 217 | 218 | no_build 219 | } 220 | } 221 | 222 | #[test] 223 | fn test_bounds_on_skipped() { 224 | #![allow(dead_code)] 225 | 226 | use std::{fmt::*, marker::PhantomData}; 227 | 228 | struct NoDebug; 229 | 230 | struct TemplatedType { 231 | _phantom: PhantomData, 232 | } 233 | 234 | impl Debug for TemplatedType 235 | where 236 | T: Debug, 237 | { 238 | fn fmt(&self, f: &mut Formatter) -> Result { 239 | write!(f, "TemplatedType") 240 | } 241 | } 242 | 243 | test_derive! { 244 | custom_debug_derive { 245 | struct WantDebug { 246 | foo: TemplatedType, 247 | #[debug(skip)] 248 | bar: Debug, 249 | } 250 | } 251 | 252 | expands to { 253 | const _: () = { 254 | impl ::core::fmt::Debug for WantDebug 255 | where 256 | TemplatedType: ::core::fmt::Debug 257 | { 258 | fn fmt(&self, fmt: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { 259 | match self { 260 | WantDebug { foo: ref __binding_0, .. } => { 261 | let mut debug_builder = fmt.debug_struct("WantDebug"); 262 | debug_builder.field("foo", __binding_0); 263 | debug_builder.finish() 264 | } 265 | } 266 | } 267 | } 268 | }; 269 | } 270 | 271 | no_build 272 | } 273 | } 274 | 275 | #[test] 276 | fn test_bounds_on_fields_only() { 277 | #![allow(dead_code)] 278 | 279 | use std::marker::PhantomData; 280 | 281 | struct NoDebug; 282 | 283 | struct TemplatedType { 284 | _phantom: PhantomData, 285 | } 286 | 287 | test_derive! { 288 | custom_debug_derive { 289 | struct WantDebug { 290 | foo: TemplatedType, 291 | bar: TemplatedType, 292 | needs_debug: T, 293 | } 294 | } 295 | 296 | expands to { 297 | const _: () = { 298 | impl ::core::fmt::Debug for WantDebug 299 | where 300 | TemplatedType: ::core::fmt::Debug, 301 | T: ::core::fmt::Debug 302 | { 303 | fn fmt(&self, fmt: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { 304 | match self { 305 | WantDebug { foo: ref __binding_0, bar: ref __binding_1, needs_debug: ref __binding_2, } => { 306 | let mut debug_builder = fmt.debug_struct("WantDebug"); 307 | debug_builder.field("foo", __binding_0); 308 | debug_builder.field("bar", __binding_1); 309 | debug_builder.field("needs_debug", __binding_2); 310 | debug_builder.finish() 311 | } 312 | } 313 | } 314 | } 315 | }; 316 | } 317 | 318 | no_build 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /examples/buffers.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::disallowed_names)] 2 | use custom_debug::{hexbuf, hexbuf_str, Debug}; 3 | 4 | #[derive(Debug)] 5 | struct Foo { 6 | #[debug(with = "hexbuf")] 7 | buf1: &'static [u8], 8 | #[debug(with = "hexbuf_str")] 9 | buf2: Vec, 10 | } 11 | 12 | fn main() { 13 | let foo = Foo { 14 | buf1: b"\0test1\0test2\0", 15 | buf2: b"\0test1\0test2\0".to_vec(), 16 | }; 17 | 18 | println!("{:#?}", foo); 19 | } 20 | -------------------------------------------------------------------------------- /examples/conditional_skip.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::disallowed_names)] 2 | use core::fmt; 3 | 4 | use custom_debug::Debug; 5 | 6 | #[derive(Debug)] 7 | struct Foo { 8 | x: i32, 9 | #[debug( 10 | skip_if = Option::is_none, 11 | with = strip_some, 12 | )] 13 | y: Option, 14 | z: i32, 15 | } 16 | 17 | fn main() { 18 | let mut foo = Foo { 19 | x: 42, 20 | y: None, 21 | z: 171, 22 | }; 23 | 24 | println!("With `y = None`:"); 25 | println!("{:#?}", foo); 26 | 27 | foo.y = Some(123); 28 | println!("With `y = Some(123)`:"); 29 | println!("{:#?}", foo); 30 | } 31 | 32 | fn strip_some(value: &Option, f: &mut fmt::Formatter) -> fmt::Result { 33 | if let Some(value) = value { 34 | value.fmt(f)?; 35 | } 36 | 37 | Ok(()) 38 | } 39 | -------------------------------------------------------------------------------- /examples/hex.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::disallowed_names)] 2 | use custom_debug::Debug; 3 | use std::fmt; 4 | 5 | #[derive(Debug)] 6 | struct Foo { 7 | #[debug(format = "{} things")] 8 | x: i32, 9 | #[debug(skip)] 10 | y: i32, 11 | #[debug(with = "hex_fmt")] 12 | z: i32, 13 | } 14 | 15 | fn hex_fmt(n: &T, f: &mut fmt::Formatter) -> fmt::Result { 16 | write!(f, "0x{:02X?}", n) 17 | } 18 | 19 | fn main() { 20 | let foo = Foo { 21 | x: 42, 22 | y: 123, 23 | z: 171, 24 | }; 25 | 26 | println!("{:#?}", foo); 27 | println!("Hidden field 'y': {}", foo.y); 28 | } 29 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | use core::fmt; 3 | 4 | /// Alias of [Debug] 5 | pub use custom_debug_derive::Debug as CustomDebug; 6 | pub use custom_debug_derive::*; 7 | 8 | /// Formats a buffer as hex using \xNN notation. 9 | pub fn hexbuf(v: &impl AsRef<[u8]>, f: &mut fmt::Formatter) -> fmt::Result { 10 | write!(f, "b\"")?; 11 | 12 | for x in v.as_ref() { 13 | write!(f, "\\x{:02x}", x)?; 14 | } 15 | 16 | write!(f, "\"")?; 17 | 18 | Ok(()) 19 | } 20 | 21 | /// Formats a buffer as hex using \xNN notation, 22 | /// except for printable ascii characters. 23 | pub fn hexbuf_str(v: &impl AsRef<[u8]>, f: &mut fmt::Formatter) -> fmt::Result { 24 | write!(f, "b\"")?; 25 | 26 | for x in v.as_ref() { 27 | match x { 28 | b'\\' => write!(f, "\\\\")?, 29 | b'"' => write!(f, "\\\"")?, 30 | b if b.is_ascii_graphic() => write!(f, "{}", *x as char)?, 31 | _ => write!(f, "\\x{:02x}", x)?, 32 | } 33 | } 34 | 35 | write!(f, "\"")?; 36 | 37 | Ok(()) 38 | } 39 | --------------------------------------------------------------------------------