├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── README.md ├── display_tree_derive ├── .gitignore ├── Cargo.toml ├── README.md └── src │ ├── attrs.rs │ ├── gen.rs │ └── lib.rs ├── src ├── display.rs ├── lib.rs └── to_display_tree_ref.rs └── tests ├── attrs.rs ├── derive.rs └── style.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "display_tree" 3 | version = "1.1.2" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | description = "Simple, automatic, and customizable tree pretty-printing" 7 | keywords = ["tree", "pretty-print", "derive", "formatting"] 8 | categories = ["value-formatting", "data-structures"] 9 | documentation = "https://docs.rs/display_tree/*/display_tree/" 10 | repository = "https://github.com/captain-camel/display_tree" 11 | 12 | [dependencies] 13 | display_tree_derive = { path = "display_tree_derive", version = "1.0.3"} 14 | 15 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `display_tree` 2 | 3 | Simple, automatic, and customizable tree pretty-printing in Rust. 4 | 5 | ![Example](https://i.ibb.co/RbpZ0Jk/Screenshot-2022-12-19-at-5-36-09-PM.png) 6 | 7 | This crate provides tools to easily pretty-print data as a tree, including a trait that represents the ability to be printed as a tree, and a derive macro to automatically implement it for your types 8 | 9 | See the [crate-level documentation](https://docs.rs/display_tree/*/display_tree) to get started. 10 | 11 | ## Examples 12 | 13 | ```rust 14 | use display_tree::{AsTree, CharSet, DisplayTree, StyleBuilder}; 15 | 16 | // A tree representing a numerical expression. 17 | #[derive(DisplayTree)] 18 | enum Expr { 19 | Int(i32), 20 | BinOp { 21 | #[node_label] 22 | op: char, 23 | #[tree] 24 | left: Box, 25 | #[tree] 26 | right: Box, 27 | }, 28 | UnaryOp { 29 | #[node_label] 30 | op: char, 31 | #[tree] 32 | arg: Box, 33 | }, 34 | } 35 | 36 | let expr: Expr = Expr::BinOp { 37 | op: '+', 38 | left: Box::new(Expr::UnaryOp { 39 | op: '-', 40 | arg: Box::new(Expr::Int(2)), 41 | }), 42 | right: Box::new(Expr::Int(7)), 43 | }; 44 | 45 | assert_eq!( 46 | format!( 47 | "{}", 48 | AsTree::new(&expr) 49 | .indentation(1) 50 | .char_set(CharSet::DOUBLE_LINE) 51 | ), 52 | concat!( 53 | "+\n", 54 | "╠═ -\n", 55 | "║ ╚═ Int\n", 56 | "║ ╚═ 2\n", 57 | "╚═ Int\n", 58 | " ╚═ 7", 59 | ), 60 | ); 61 | ``` -------------------------------------------------------------------------------- /display_tree_derive/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock -------------------------------------------------------------------------------- /display_tree_derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "display_tree_derive" 3 | version = "1.0.3" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | description = "Derive macro for `display_tree` crate" 7 | documentation = "https://docs.rs/display_tree_derive/*/display_tree_derive/" 8 | repository = "https://github.com/captain-camel/display_tree/tree/master/display_tree_derive" 9 | 10 | [dependencies] 11 | syn = "1.0.105" 12 | quote = "1.0.21" 13 | proc-macro2 = "1.0.47" 14 | proc-macro-error = "1.0.4" 15 | 16 | [lib] 17 | proc-macro = true 18 | 19 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -------------------------------------------------------------------------------- /display_tree_derive/README.md: -------------------------------------------------------------------------------- 1 | This crate contains derive macros for the [`display_tree`](https://docs.rs/display_tree/*/display_tree) crate. -------------------------------------------------------------------------------- /display_tree_derive/src/attrs.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::Span; 2 | 3 | pub mod node { 4 | use syn::spanned::Spanned; 5 | 6 | use super::{AttributeParseError, AttributeParseErrorKind}; 7 | 8 | pub const NODE_LABEL: &str = "node_label"; 9 | 10 | pub enum Attribute { 11 | NodeLabel(String), 12 | } 13 | 14 | impl Attribute { 15 | pub fn parse(attribute: &syn::Attribute) -> Option> { 16 | // FIXME: This should probably be parsed manually, currently it will miss 17 | // recognized attributes that are in the wrong format. 18 | let meta = attribute.parse_meta().ok()?; 19 | 20 | match meta.path().get_ident()?.to_string().as_str() { 21 | NODE_LABEL => { 22 | if let syn::Meta::NameValue(syn::MetaNameValue { lit: literal, .. }) = meta { 23 | if let syn::Lit::Str(label) = literal { 24 | Some(Ok(Self::NodeLabel(label.value()))) 25 | } else { 26 | Some(Err(AttributeParseError::new( 27 | AttributeParseErrorKind::ExpectedStringLiteral(NODE_LABEL), 28 | attribute.span(), 29 | ))) 30 | } 31 | } else { 32 | Some(Err(AttributeParseError::new( 33 | AttributeParseErrorKind::ExpectedEqual(NODE_LABEL), 34 | attribute.span(), 35 | ))) 36 | } 37 | } 38 | _ => None, 39 | } 40 | } 41 | } 42 | 43 | pub struct Config { 44 | pub node_label: Option, 45 | } 46 | 47 | impl Config { 48 | pub fn default() -> Self { 49 | Self { node_label: None } 50 | } 51 | } 52 | 53 | impl Config { 54 | pub fn from(attributes: &[syn::Attribute]) -> Result { 55 | attributes.iter().flat_map(Attribute::parse).try_fold( 56 | Self::default(), 57 | |config, attribute| { 58 | Ok(match attribute? { 59 | Attribute::NodeLabel(label) => Self { 60 | node_label: Some(label), 61 | ..config 62 | }, 63 | }) 64 | }, 65 | ) 66 | } 67 | } 68 | } 69 | 70 | pub mod field { 71 | use syn::spanned::Spanned; 72 | 73 | use super::{AttributeParseError, AttributeParseErrorKind}; 74 | 75 | pub const FIELD_LABEL: &str = "field_label"; 76 | pub const IGNORE_FIELD: &str = "ignore_field"; 77 | pub const NODE_LABEL: &str = "node_label"; 78 | pub const TREE: &str = "tree"; 79 | 80 | pub enum Attribute { 81 | FieldLabel(Option), 82 | IgnoreField, 83 | NodeLabel, 84 | Tree, 85 | } 86 | 87 | impl Attribute { 88 | pub fn parse(attribute: &syn::Attribute) -> Option> { 89 | // FIXME: This should probably be parsed manually, currently it will miss 90 | // recognized attributes that are in the wrong format. 91 | let meta = attribute.parse_meta().ok()?; 92 | 93 | Some(match meta.path().get_ident()?.to_string().as_str() { 94 | FIELD_LABEL => match meta { 95 | syn::Meta::Path(_) => Ok(Self::FieldLabel(None)), 96 | syn::Meta::NameValue(syn::MetaNameValue { lit: literal, .. }) => { 97 | if let syn::Lit::Str(label) = literal { 98 | Ok(Self::FieldLabel(Some(label.value()))) 99 | } else { 100 | Err(AttributeParseError::new( 101 | AttributeParseErrorKind::ExpectedStringLiteral(FIELD_LABEL), 102 | attribute.span(), 103 | )) 104 | } 105 | } 106 | _ => Err(AttributeParseError::new( 107 | AttributeParseErrorKind::Malformed(FIELD_LABEL), 108 | attribute.span(), 109 | )), 110 | }, 111 | IGNORE_FIELD => { 112 | // The `ignore_field` attribute should only be a path, no arguments. 113 | if matches!(meta, syn::Meta::Path(_)) { 114 | Ok(Self::IgnoreField) 115 | } else { 116 | Err(AttributeParseError::new( 117 | AttributeParseErrorKind::UnexpectedArgument(IGNORE_FIELD), 118 | attribute.span(), 119 | )) 120 | } 121 | } 122 | NODE_LABEL => { 123 | // The `node_label` attribute should only be a path, no arguments. 124 | if matches!(meta, syn::Meta::Path(_)) { 125 | Ok(Self::NodeLabel) 126 | } else { 127 | Err(AttributeParseError::new( 128 | AttributeParseErrorKind::UnexpectedArgument(NODE_LABEL), 129 | attribute.span(), 130 | )) 131 | } 132 | } 133 | TREE => { 134 | // The `tree` attribute should only be a path, no arguments. 135 | if matches!(meta, syn::Meta::Path(_)) { 136 | Ok(Self::Tree) 137 | } else { 138 | Err(AttributeParseError::new( 139 | AttributeParseErrorKind::UnexpectedArgument(TREE), 140 | attribute.span(), 141 | )) 142 | } 143 | } 144 | _ => return None, 145 | }) 146 | } 147 | } 148 | 149 | pub struct Config { 150 | pub field_label: FieldLabel, 151 | pub is_ignored: bool, 152 | pub is_node_label: bool, 153 | pub is_tree: bool, 154 | } 155 | 156 | pub enum FieldLabel { 157 | None, 158 | Default, 159 | Custom(String), 160 | } 161 | 162 | impl Config { 163 | pub fn default() -> Self { 164 | Self { 165 | field_label: FieldLabel::None, 166 | is_ignored: false, 167 | is_node_label: false, 168 | is_tree: false, 169 | } 170 | } 171 | } 172 | 173 | impl Config { 174 | pub fn from(attributes: &[syn::Attribute]) -> Result { 175 | let config = attributes.iter().flat_map(Attribute::parse).try_fold( 176 | Self::default(), 177 | |config, attribute| { 178 | Ok(match attribute? { 179 | Attribute::FieldLabel(label) => Self { 180 | field_label: match label { 181 | Some(label) => FieldLabel::Custom(label), 182 | None => FieldLabel::Default, 183 | }, 184 | ..config 185 | }, 186 | Attribute::IgnoreField => Self { 187 | is_ignored: true, 188 | ..config 189 | }, 190 | Attribute::NodeLabel => Self { 191 | is_node_label: true, 192 | ..config 193 | }, 194 | Attribute::Tree => Self { 195 | is_tree: true, 196 | ..config 197 | }, 198 | }) 199 | }, 200 | )?; 201 | 202 | if config.is_node_label && config.is_ignored { 203 | return Err(AttributeParseError::new( 204 | AttributeParseErrorKind::Confict(NODE_LABEL, IGNORE_FIELD), 205 | attributes.first().span(), 206 | )); 207 | } 208 | if config.is_ignored && config.is_tree { 209 | return Err(AttributeParseError::new( 210 | AttributeParseErrorKind::Confict(IGNORE_FIELD, TREE), 211 | attributes.first().span(), 212 | )); 213 | } 214 | 215 | Ok(config) 216 | } 217 | } 218 | } 219 | 220 | pub struct AttributeParseError { 221 | kind: AttributeParseErrorKind, 222 | pub span: Span, 223 | } 224 | 225 | pub enum AttributeParseErrorKind { 226 | Confict(&'static str, &'static str), 227 | ExpectedEqual(&'static str), 228 | ExpectedStringLiteral(&'static str), 229 | Malformed(&'static str), 230 | UnexpectedArgument(&'static str), 231 | } 232 | 233 | impl AttributeParseError { 234 | fn new(kind: AttributeParseErrorKind, span: Span) -> Self { 235 | Self { kind, span } 236 | } 237 | } 238 | 239 | impl std::fmt::Display for AttributeParseError { 240 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 241 | let string = match self.kind { 242 | AttributeParseErrorKind::Confict(first, second) => { 243 | format!("conflicting helper attributes: cannot apply `{first}` and `{second}` to the same field") 244 | } 245 | AttributeParseErrorKind::ExpectedEqual(attribute) => { 246 | format!("expected `=`: `{attribute}` attribute requires an argument") 247 | } 248 | AttributeParseErrorKind::ExpectedStringLiteral(attribute) => { 249 | format!("expected string literal in `{attribute}` attribute") 250 | } 251 | AttributeParseErrorKind::Malformed(attribute) => { 252 | format!("malformed `{attribute} attribute`") 253 | } 254 | AttributeParseErrorKind::UnexpectedArgument(attribute) => { 255 | format!("`{attribute}` attribute takes no arguments") 256 | } 257 | }; 258 | 259 | write!(f, "{string}") 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /display_tree_derive/src/gen.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream as TokenStream2; 2 | use proc_macro_error::{abort, abort_call_site}; 3 | use quote::{quote, quote_spanned, TokenStreamExt}; 4 | use syn::spanned::Spanned; 5 | 6 | use super::attrs; 7 | 8 | struct ConstIdent(&'static str); 9 | 10 | impl quote::ToTokens for ConstIdent { 11 | fn to_tokens(&self, tokens: &mut TokenStream2) { 12 | tokens.append(syn::Ident::new(self.0, proc_macro2::Span::call_site())) 13 | } 14 | } 15 | 16 | const FORMATTER_PARAM: ConstIdent = ConstIdent("__display_tree_f"); 17 | const STYLE_PARAM: ConstIdent = ConstIdent("__display_tree_style"); 18 | 19 | pub fn generate(ast: syn::DeriveInput) -> TokenStream2 { 20 | let fn_inner = match ast.data { 21 | syn::Data::Struct(data) => { 22 | let config = match attrs::node::Config::from(&ast.attrs) { 23 | Ok(config) => config, 24 | Err(error) => abort!(error.span, "{}", error), 25 | }; 26 | generate_struct_field_writes(&ast.ident, data.fields, config) 27 | } 28 | syn::Data::Enum(data) => { 29 | let arms = data.variants.into_iter().map(generate_arm); 30 | quote! { 31 | match self { 32 | #( 33 | #arms 34 | ),* 35 | } 36 | } 37 | } 38 | _ => abort_call_site!("`DisplayTree` can only be derived for `struct`s and `enum`s"), 39 | }; 40 | 41 | let ty = ast.ident; 42 | let generics = ast.generics; 43 | 44 | quote! { 45 | impl #generics ::display_tree::DisplayTree for #ty #generics { 46 | fn fmt(&self, #FORMATTER_PARAM: &mut std::fmt::Formatter, #STYLE_PARAM: ::display_tree::Style) -> ::std::fmt::Result { 47 | #fn_inner 48 | Ok(()) 49 | } 50 | } 51 | } 52 | } 53 | 54 | pub fn generate_struct_field_writes<'a>( 55 | ident: &syn::Ident, 56 | fields: syn::Fields, 57 | struct_config: attrs::node::Config, 58 | ) -> TokenStream2 { 59 | let mut node_label_member_expression: Option = None; 60 | 61 | let mut leaf_expressions: Vec = Vec::new(); 62 | for (index, field) in fields.iter().enumerate() { 63 | let field_member_expression = if let Some(ident) = &field.ident { 64 | quote! { self.#ident } 65 | } else { 66 | let index = syn::Index::from(index); 67 | quote! { self.#index } 68 | }; 69 | 70 | let field_config = match attrs::field::Config::from(&field.attrs) { 71 | Ok(config) => config, 72 | Err(error) => abort!(error.span, "{}", error), 73 | }; 74 | 75 | if field_config.is_node_label { 76 | if let Some(label) = struct_config.node_label { 77 | abort! { field.span(), 78 | "conflicting helper attributes: cannot apply `{}` to field because variant `{}` already has a label specified", 79 | attrs::field::NODE_LABEL, 80 | ident; 81 | note = "variant `{}` has label \"{}\"", ident, label; 82 | } 83 | } 84 | 85 | node_label_member_expression = Some(field_member_expression.clone()); 86 | 87 | // If the field is marked as a tree, it should still be written in adition to 88 | // being the node label. 89 | if !field_config.is_tree { 90 | continue; 91 | } 92 | } 93 | if field_config.is_ignored { 94 | continue; 95 | } 96 | 97 | use attrs::field::FieldLabel; 98 | let leaf_expression_format = match field_config.field_label { 99 | FieldLabel::Custom(label) => format!("{label}: {{}}"), 100 | FieldLabel::Default => { 101 | if let Some(identifier) = &field.ident { 102 | format!("{}: {{}}", identifier.to_string()) 103 | } else { 104 | abort! { field.span(), 105 | "cannot apply `{}` attribute to field because it is unnamed", 106 | attrs::field::FIELD_LABEL, 107 | } 108 | } 109 | } 110 | FieldLabel::None => format!("{{}}"), 111 | }; 112 | 113 | let leaf_expression = if field_config.is_tree { 114 | // If the field is a tree, display it as one. 115 | // Use the span of the field so that errors show in the correct location. 116 | quote_spanned! { field.span() => 117 | ::std::format!( 118 | #leaf_expression_format, 119 | ::display_tree::AsTree::with_style( 120 | ::display_tree::to_display_tree_ref::ToDisplayTreeRef::to_display_tree(&#field_member_expression), 121 | #STYLE_PARAM 122 | ) 123 | ) 124 | } 125 | } else { 126 | // Otherwise try to use `Display`. 127 | // Use the span of the field so that errors show in the correct location. 128 | quote_spanned! { field.span() => 129 | #STYLE_PARAM.leaf_style.apply(&::std::format!(#leaf_expression_format, #field_member_expression)) 130 | } 131 | }; 132 | 133 | leaf_expressions.push(leaf_expression); 134 | } 135 | 136 | let node_label_string_ref = if let Some(expression) = node_label_member_expression { 137 | // One of the fields of the variant should be used as the label. 138 | quote! { 139 | &format!("{}", #expression) 140 | } 141 | } else if let Some(label) = struct_config.node_label { 142 | // The variant has a custom label. 143 | quote! { 144 | #label 145 | } 146 | } else { 147 | // If not specified, the node label should be a string containing the name of 148 | // the variant. 149 | let variant_string = ident.to_string(); 150 | quote! { 151 | #variant_string 152 | } 153 | }; 154 | 155 | generate_field_writes(leaf_expressions, node_label_string_ref) 156 | } 157 | 158 | pub fn generate_arm(variant: syn::Variant) -> TokenStream2 { 159 | let config = match attrs::node::Config::from(&variant.attrs) { 160 | Ok(config) => config, 161 | Err(error) => abort!(error.span, "{}", error), 162 | }; 163 | 164 | let variant_ident = &variant.ident; 165 | 166 | match &variant.fields { 167 | syn::Fields::Named(fields) => { 168 | let field_bindings = fields.named.iter().map(|field| { 169 | field 170 | .ident 171 | .as_ref() 172 | .expect("named fields should have identifiers.") 173 | }); 174 | 175 | let arm_inner = generate_enum_field_writes(&variant, field_bindings.clone(), config); 176 | 177 | quote! { 178 | Self::#variant_ident { #(#field_bindings),* } => { 179 | #arm_inner 180 | } 181 | } 182 | } 183 | 184 | syn::Fields::Unnamed(fields) => { 185 | let field_bindings = (0..fields.unnamed.len()) 186 | .map(|index| quote::format_ident!("_{}", index)) 187 | .collect::>(); 188 | 189 | let arm_inner = generate_enum_field_writes(&variant, field_bindings.iter(), config); 190 | 191 | quote! { 192 | Self::#variant_ident(#(#field_bindings),*) => { 193 | #arm_inner 194 | } 195 | } 196 | } 197 | 198 | syn::Fields::Unit => { 199 | let arm_inner = generate_enum_field_writes(&variant, std::iter::empty(), config); 200 | 201 | quote! { 202 | Self::#variant_ident => { 203 | #arm_inner 204 | } 205 | } 206 | } 207 | } 208 | } 209 | 210 | pub fn generate_enum_field_writes<'a>( 211 | variant: &syn::Variant, 212 | field_bindings: impl Iterator, 213 | variant_config: attrs::node::Config, 214 | ) -> TokenStream2 { 215 | let mut node_label_binding: Option<&syn::Ident> = None; 216 | 217 | let mut leaf_expressions: Vec = Vec::new(); 218 | for (field, binding) in std::iter::zip(variant.fields.iter(), field_bindings) { 219 | let field_config = match attrs::field::Config::from(&field.attrs) { 220 | Ok(config) => config, 221 | Err(error) => abort!(error.span, "{}", error), 222 | }; 223 | 224 | if field_config.is_node_label { 225 | if let Some(label) = variant_config.node_label { 226 | abort! { field.span(), 227 | "conflicting helper attributes: cannot apply `{}` attribute to field because variant `{}` already has a label specified", 228 | attrs::field::NODE_LABEL, 229 | variant.ident; 230 | note = "variant `{}` has label \"{}\"", variant.ident, label; 231 | } 232 | } 233 | 234 | node_label_binding = Some(&binding); 235 | 236 | // If the field is marked as a tree, it should still be written in adition to 237 | // being the node label. 238 | if !field_config.is_tree { 239 | continue; 240 | } 241 | } 242 | if field_config.is_ignored { 243 | continue; 244 | } 245 | 246 | use attrs::field::FieldLabel; 247 | let leaf_expression_format = match field_config.field_label { 248 | FieldLabel::Custom(label) => format!("{label}: {{}}"), 249 | FieldLabel::Default => { 250 | if let Some(identifier) = &field.ident { 251 | format!("{}: {{}}", identifier.to_string()) 252 | } else { 253 | abort! { field.span(), 254 | "cannot apply `{}` attribute to field because it is unnamed", 255 | attrs::field::FIELD_LABEL, 256 | } 257 | } 258 | } 259 | FieldLabel::None => format!("{{}}"), 260 | }; 261 | 262 | let leaf_expression = if field_config.is_tree { 263 | // If the field is a tree, display it as one. 264 | // Use the span of the field so that errors show in the correct location. 265 | quote_spanned! { field.span() => 266 | ::std::format!( 267 | #leaf_expression_format, 268 | ::display_tree::AsTree::with_style( 269 | ::display_tree::to_display_tree_ref::ToDisplayTreeRef::to_display_tree(#binding), 270 | #STYLE_PARAM 271 | ) 272 | ) 273 | } 274 | } else { 275 | // Otherwise try to use `Display`. 276 | // Use the span of the field so that errors show in the correct location. 277 | quote_spanned! { field.span() => 278 | #STYLE_PARAM.leaf_style.apply(&::std::format!(#leaf_expression_format, #binding)) 279 | } 280 | }; 281 | 282 | leaf_expressions.push(leaf_expression); 283 | } 284 | 285 | let node_label_string_ref = if let Some(binding) = node_label_binding { 286 | // One of the fields of the variant should be used as the label. 287 | quote! { 288 | &format!("{}", #binding) 289 | } 290 | } else if let Some(label) = variant_config.node_label { 291 | // The variant has a custom label. 292 | quote! { 293 | #label 294 | } 295 | } else { 296 | // If not specified, the node label should be a string containing the name of 297 | // the variant. 298 | let variant_string = variant.ident.to_string(); 299 | quote! { 300 | #variant_string 301 | } 302 | }; 303 | 304 | generate_field_writes(leaf_expressions, node_label_string_ref) 305 | } 306 | 307 | fn generate_field_writes( 308 | leaf_expressions: Vec, 309 | node_label_string_ref: TokenStream2, 310 | ) -> TokenStream2 { 311 | let mut field_writes: Vec = Vec::with_capacity(leaf_expressions.len()); 312 | for (index, leaf_expression) in leaf_expressions.iter().enumerate() { 313 | let leaf_first_line_connectors_string = if index == leaf_expressions.len() - 1 { 314 | quote! { 315 | ::std::format!( 316 | "{}{} ", 317 | #STYLE_PARAM.char_set.end_connector, 318 | std::iter::repeat(#STYLE_PARAM.char_set.horizontal) 319 | .take(#STYLE_PARAM.indentation as usize) 320 | .collect::() 321 | ) 322 | } 323 | } else { 324 | quote! { 325 | ::std::format!( 326 | "{}{} ", 327 | #STYLE_PARAM.char_set.connector, 328 | std::iter::repeat(#STYLE_PARAM.char_set.horizontal) 329 | .take(#STYLE_PARAM.indentation as usize) 330 | .collect::() 331 | ) 332 | } 333 | }; 334 | 335 | let leaf_other_lines_connectors_string = if index == leaf_expressions.len() - 1 { 336 | quote! { 337 | ::std::format!( 338 | " {} ", 339 | std::iter::repeat(' ') 340 | .take(#STYLE_PARAM.indentation as usize) 341 | .collect::() 342 | ) 343 | } 344 | } else { 345 | quote! { 346 | ::std::format!( 347 | "{}{} ", 348 | #STYLE_PARAM.char_set.vertical, 349 | std::iter::repeat(' ') 350 | .take(#STYLE_PARAM.indentation as usize) 351 | .collect::() 352 | ) 353 | } 354 | }; 355 | 356 | field_writes.push(quote! { 357 | let s = #leaf_expression; 358 | let mut lines = s.lines(); 359 | 360 | write!( 361 | #FORMATTER_PARAM, 362 | "\n{}{}", 363 | #STYLE_PARAM.branch_style.apply(&#leaf_first_line_connectors_string), 364 | lines.next().unwrap_or_default(), 365 | )?; 366 | 367 | for line in lines { 368 | write!( 369 | #FORMATTER_PARAM, 370 | "\n{}{}", 371 | #STYLE_PARAM.branch_style.apply(&#leaf_other_lines_connectors_string), 372 | line, 373 | )?; 374 | } 375 | }) 376 | } 377 | 378 | quote! { 379 | write!(#FORMATTER_PARAM, "{}", #STYLE_PARAM.leaf_style.apply(#node_label_string_ref))?; 380 | #(#field_writes)* 381 | } 382 | } 383 | -------------------------------------------------------------------------------- /display_tree_derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate contains derive macros for the 2 | //! [`display_tree`](https://docs.rs/display_tree/*/display_tree) crate. 3 | 4 | mod attrs; 5 | mod gen; 6 | 7 | /// Derive marco for the [`DisplayTree` trait](https://docs.rs/display_tree/*/display_tree/trait.DisplayTree.html). 8 | /// 9 | /// See the [`DisplayTree` documentation](https://docs.rs/display_tree/*/display_tree/trait.DisplayTree.html) for more information. 10 | #[proc_macro_derive( 11 | DisplayTree, 12 | attributes(field_label, ignore_field, node_label, optional_field, tree) 13 | )] 14 | #[proc_macro_error::proc_macro_error] 15 | pub fn derive_display_tree(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream { 16 | let ast = syn::parse::(tokens) 17 | .expect("rust should ensure `derive` is only applied to types"); 18 | gen::generate(ast).into() 19 | } 20 | -------------------------------------------------------------------------------- /src/display.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use super::DisplayTree; 4 | 5 | /// A helper struct for formatting a type that implements [`DisplayTree`]. 6 | /// 7 | /// [`AsTree`] stores a reference to the type to format. It implements 8 | /// [`std::fmt::Display`], so it can be used in [`println!`], [`format!`], 9 | /// etc... 10 | /// 11 | /// # Styling 12 | /// 13 | /// [`AsTree`] controls the way a tree is styled when it is formatted. The style 14 | /// can be customized using builder methods. See [`Style`] for the different 15 | /// aspects that can be customized. 16 | /// 17 | /// **Note:** [`StyleBuilder`] must be in scope to use the builder methods. 18 | /// 19 | /// **Note:** Some styling options use ANSI escape codes and therefore will only 20 | /// work where they are supported. See [`TextStyle`] for more information. 21 | /// 22 | /// # Examples 23 | /// 24 | /// ``` 25 | /// use display_tree::{AsTree, DisplayTree}; 26 | /// 27 | /// #[derive(DisplayTree)] 28 | /// struct Tree { 29 | /// a: i32, 30 | /// b: i32, 31 | /// } 32 | /// 33 | /// let tree = Tree { a: 1, b: 2 }; 34 | /// 35 | /// assert_eq!( 36 | /// format!("{}", AsTree::new(&tree)), 37 | /// "Tree\n\ 38 | /// ├── 1\n\ 39 | /// └── 2" 40 | /// ); 41 | /// ``` 42 | /// 43 | /// Specifying a style: 44 | /// 45 | /// ``` 46 | /// use display_tree::{AsTree, CharSet, DisplayTree, StyleBuilder}; 47 | /// 48 | /// #[derive(DisplayTree)] 49 | /// struct Tree { 50 | /// a: i32, 51 | /// b: i32, 52 | /// } 53 | /// 54 | /// let tree = Tree { a: 1, b: 2 }; 55 | /// 56 | /// assert_eq!( 57 | /// format!("{}", AsTree::new(&tree).char_set(CharSet::DOUBLE_LINE)), 58 | /// "Tree\n\ 59 | /// ╠══ 1\n\ 60 | /// ╚══ 2" 61 | /// ); 62 | /// ``` 63 | pub struct AsTree<'a, T: DisplayTree> { 64 | tree: &'a T, 65 | style: Style, 66 | } 67 | 68 | impl<'a, T: DisplayTree> AsTree<'a, T> { 69 | /// Creates a wrapper around a type that implements [`DisplayTree`], 70 | /// allowing it to be formatted. 71 | /// 72 | /// # Examples 73 | /// 74 | /// ``` 75 | /// use display_tree::{AsTree, DisplayTree}; 76 | /// 77 | /// #[derive(DisplayTree)] 78 | /// struct Tree; 79 | /// 80 | /// let as_tree = AsTree::new(&Tree); 81 | /// ``` 82 | pub fn new(tree: &'a T) -> Self { 83 | Self { 84 | tree, 85 | style: Style::default(), 86 | } 87 | } 88 | 89 | /// Creates a wrapper around a type that implements [`DisplayTree`], 90 | /// allowing it to be formatted with the given style. 91 | /// 92 | /// # Examples 93 | /// 94 | /// ``` 95 | /// use display_tree::{AsTree, DisplayTree, Style}; 96 | /// 97 | /// #[derive(DisplayTree)] 98 | /// struct Tree; 99 | /// 100 | /// let as_styled_tree = AsTree::with_style(&Tree, Style::default()); 101 | /// ``` 102 | pub fn with_style(tree: &'a T, style: Style) -> Self { 103 | Self { tree, style } 104 | } 105 | } 106 | 107 | impl<'a, T: DisplayTree> StyleBuilder for AsTree<'a, T> { 108 | fn style_mut(&mut self) -> &mut Style { 109 | &mut self.style 110 | } 111 | } 112 | 113 | impl<'a, T: DisplayTree> fmt::Display for AsTree<'a, T> { 114 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 115 | self.tree.fmt(f, self.style) 116 | } 117 | } 118 | 119 | /// A type that describes the way a type that implements [`DisplayTree`] should 120 | /// be formatted. 121 | /// 122 | /// Prefer using builder methods, either on [`Style`] or [`AsTree`], over 123 | /// constructing an instance of [`Style`] manually. 124 | /// 125 | /// **Note:** [`StyleBuilder`] must be in scope to use builder methods. 126 | /// 127 | /// # Examples 128 | /// 129 | /// ``` 130 | /// use display_tree::{CharSet, Color, Style, StyleBuilder}; 131 | /// 132 | /// let style = Style::default() 133 | /// .leaf_color(Color::Blue) 134 | /// .branch_background_color(Color::Red) 135 | /// .indentation(4) 136 | /// .char_set(CharSet::SINGLE_LINE_CURVED); 137 | /// ``` 138 | #[derive(Clone, Copy)] 139 | pub struct Style { 140 | /// The [`CharSet`] making up the branches of the tree. 141 | pub char_set: CharSet, 142 | /// The indentation of each node. 143 | pub indentation: u32, 144 | /// The style of the leaves of the tree. See [`TextStyle`] for more 145 | /// information. 146 | pub leaf_style: TextStyle, 147 | /// The style of the branches of the tree. See [`TextStyle`] for more 148 | /// information. 149 | pub branch_style: TextStyle, 150 | } 151 | 152 | impl StyleBuilder for Style { 153 | fn style_mut(&mut self) -> &mut Style { 154 | self 155 | } 156 | } 157 | 158 | impl Default for Style { 159 | /// The default [`Style`] used if none is specified. 160 | /// 161 | /// Default values: 162 | /// - [`char_set`](Style::char_set): [`CharSet::SINGLE_LINE`] 163 | /// - [`indentation`](Style::indentation): `2` 164 | /// - [`leaf_style`](Style::leaf_style): [`TextStyle::default()`] 165 | /// - [`branch_style`](Style::branch_style): [`TextStyle::default()`] 166 | fn default() -> Self { 167 | Self { 168 | char_set: CharSet::SINGLE_LINE, 169 | indentation: 2, 170 | leaf_style: TextStyle::default(), 171 | branch_style: TextStyle::default(), 172 | } 173 | } 174 | } 175 | 176 | /// A type that described how text will be rendered. 177 | /// 178 | /// **Note:** [`TextStyle`] uses ANSI escape codes, so it should not be used 179 | /// anywhere they are not supported. Support for individual fields may also vary 180 | /// by terminal. 181 | #[derive(Clone, Copy, Default)] 182 | pub struct TextStyle { 183 | /// The text color. [`None`] will not apply any color 184 | pub text_color: Option, 185 | /// The background color. [`None`] will not apply any background color. 186 | pub background_color: Option, 187 | /// Whether the text is bold. (Might be rendered as increased intensity.) 188 | pub is_bold: bool, 189 | /// Whether the text has decreased intensity. 190 | pub is_faint: bool, 191 | /// Whether the text is italicised. 192 | pub is_italic: bool, 193 | /// Whether the text is underlined. 194 | pub is_underlined: bool, 195 | /// Whether the text is crossed-out. 196 | pub is_strikethrough: bool, 197 | } 198 | 199 | impl TextStyle { 200 | /// Applies the style to a string. 201 | /// 202 | /// [`apply()`](TextStyle::apply()) should not be called unless you are 203 | /// manually implementing [`DisplayTree`]. It is used in derived 204 | /// [`DisplayTree`] implementations. 205 | pub fn apply(&self, string: &str) -> String { 206 | use std::borrow::Cow; 207 | 208 | let mut ansi_codes: Vec> = Vec::new(); 209 | 210 | if let Some(text_color) = self.text_color { 211 | ansi_codes.push(match text_color { 212 | Color::Black => "30".into(), 213 | Color::Red => "31".into(), 214 | Color::Green => "32".into(), 215 | Color::Yellow => "33".into(), 216 | Color::Blue => "34".into(), 217 | Color::Magenta => "35".into(), 218 | Color::Cyan => "36".into(), 219 | Color::White => "37".into(), 220 | Color::Rgb(r, g, b) => format!("38;2;{r};{g};{b}").into(), 221 | }) 222 | } 223 | 224 | if let Some(background_color) = self.background_color { 225 | ansi_codes.push(match background_color { 226 | Color::Black => "40".into(), 227 | Color::Red => "41".into(), 228 | Color::Green => "42".into(), 229 | Color::Yellow => "43".into(), 230 | Color::Blue => "44".into(), 231 | Color::Magenta => "45".into(), 232 | Color::Cyan => "46".into(), 233 | Color::White => "47".into(), 234 | Color::Rgb(r, g, b) => format!("48;2;{r};{g};{b}").into(), 235 | }) 236 | } 237 | 238 | if self.is_bold { 239 | ansi_codes.push("1".into()) 240 | } 241 | 242 | if self.is_faint { 243 | ansi_codes.push("2".into()) 244 | } 245 | 246 | if self.is_italic { 247 | ansi_codes.push("3".into()) 248 | } 249 | 250 | if self.is_underlined { 251 | ansi_codes.push("4".into()) 252 | } 253 | 254 | if self.is_strikethrough { 255 | ansi_codes.push("9".into()) 256 | } 257 | 258 | if !ansi_codes.is_empty() { 259 | let escape_sequences = ansi_codes 260 | .into_iter() 261 | .map(|code| format!("\x1b[{code}m")) 262 | .collect::(); 263 | format!("{escape_sequences}{string}\x1b[0m") 264 | } else { 265 | string.to_owned() 266 | } 267 | } 268 | } 269 | 270 | /// An ANSI color that a tree can be styled with. 271 | #[derive(Clone, Copy)] 272 | pub enum Color { 273 | /// ANSI color #0. Exact color depends on terminal. 274 | Black, 275 | /// ANSI color #1. Exact color depends on terminal. 276 | Red, 277 | /// ANSI color #2. Exact color depends on terminal. 278 | Green, 279 | /// ANSI color #3. Exact color depends on terminal. 280 | Yellow, 281 | /// ANSI color #4. Exact color depends on terminal. 282 | Blue, 283 | /// ANSI color #5. Exact color depends on terminal. 284 | Magenta, 285 | /// ANSI color #6. Exact color depends on terminal. 286 | Cyan, 287 | /// ANSI color #7. Exact color depends on terminal. 288 | White, 289 | /// A color with custom RGB values. 290 | /// 291 | /// **Note:** Truecolor support is required for this variant. [`Color::Rgb`] 292 | /// will not work properly if Truecolor is not supported in your terminal. 293 | /// In some cases it may be rendered as an 8-bit color if your terminal 294 | /// supports 256 colors. 295 | Rgb(u8, u8, u8), 296 | } 297 | 298 | /// A set of [`char`]s used for formatting a type that implements 299 | /// [`DisplayTree`]. 300 | /// 301 | /// These are the characters that make up the text that connects the nodes of 302 | /// the tree. 303 | /// 304 | /// [`CharSet`] provides a few built-in sets via associated constants, but you 305 | /// can construct your own if needed. 306 | /// 307 | /// # Examples 308 | /// 309 | /// ``` 310 | /// let char_set = display_tree::CharSet { 311 | /// horizontal: '─', 312 | /// vertical: '│', 313 | /// connector: '├', 314 | /// end_connector: '└', 315 | /// }; 316 | /// ``` 317 | #[derive(Clone, Copy)] 318 | pub struct CharSet { 319 | /// The characters used in the horizontal portion of a branch. 320 | /// 321 | /// Should resemble a plain horizontal line, eg. '─'. 322 | pub horizontal: char, 323 | /// The character used in the space between branches in place of 324 | /// [`connector`](CharSet::connector). 325 | /// 326 | /// Should resemble a plain vertical line, eg. '│'. 327 | pub vertical: char, 328 | /// The character connecting the vertical and horizontal portions of a 329 | /// branch. 330 | /// 331 | /// Should resemble a vertical line with an offshoot on the right, eg. '├'. 332 | pub connector: char, 333 | /// The character connecting the vertical and horizontal portions of the 334 | /// last branch under a node. 335 | /// 336 | /// Should resemble an "L" shape, eg. '└'. 337 | pub end_connector: char, 338 | } 339 | 340 | impl CharSet { 341 | /// Regular Unicode box-drawing characters. 342 | pub const SINGLE_LINE: Self = Self { 343 | horizontal: '─', 344 | vertical: '│', 345 | connector: '├', 346 | end_connector: '└', 347 | }; 348 | 349 | /// Bold Unicode box-drawing characters. 350 | pub const SINGLE_LINE_BOLD: Self = Self { 351 | horizontal: '━', 352 | vertical: '┃', 353 | connector: '┣', 354 | end_connector: '┗', 355 | }; 356 | 357 | /// Curved Unicode box-drawing characters. 358 | pub const SINGLE_LINE_CURVED: Self = Self { 359 | horizontal: '─', 360 | vertical: '│', 361 | connector: '├', 362 | end_connector: '╰', 363 | }; 364 | 365 | /// Double Unicode box-drawing characters. 366 | pub const DOUBLE_LINE: Self = Self { 367 | horizontal: '═', 368 | vertical: '║', 369 | connector: '╠', 370 | end_connector: '╚', 371 | }; 372 | 373 | /// ASCII characters. 374 | pub const ASCII: Self = Self { 375 | horizontal: '-', 376 | vertical: '|', 377 | connector: '|', 378 | end_connector: '`', 379 | }; 380 | } 381 | 382 | /// A trait that provides builder methods for constructing an instance of 383 | /// [`Style`]. 384 | /// 385 | /// [`StyleBuilder`] is implemented for [`Style`] and [`AsTree`], so you can use 386 | /// those types to construct an instance of [`Style`]. 387 | /// 388 | /// Do not implement [`StyleBuilder`] for any new types. 389 | pub trait StyleBuilder: Sized { 390 | #[doc(hidden)] 391 | fn style_mut(&mut self) -> &mut Style; 392 | 393 | /// Sets the [`CharSet`] making up the branches of the tree. 394 | /// 395 | /// See [`CharSet`] for more information. 396 | /// 397 | /// # Examples 398 | /// 399 | /// ``` 400 | /// use display_tree::{AsTree, CharSet, DisplayTree, StyleBuilder}; 401 | /// 402 | /// #[derive(DisplayTree)] 403 | /// struct Tree { 404 | /// a: i32, 405 | /// b: i32, 406 | /// } 407 | /// 408 | /// let tree = Tree { a: 1, b: 2 }; 409 | /// 410 | /// assert_eq!( 411 | /// format!( 412 | /// "{}", 413 | /// // Use ASCII characters instead of the default Unicode ones. 414 | /// AsTree::new(&tree).char_set(CharSet::ASCII), 415 | /// ), 416 | /// "Tree\n\ 417 | /// |-- 1\n\ 418 | /// `-- 2", 419 | /// ); 420 | /// ``` 421 | fn char_set(mut self, char_set: CharSet) -> Self { 422 | self.style_mut().char_set = char_set; 423 | self 424 | } 425 | 426 | /// Sets the indentation of each node. 427 | /// 428 | /// More specifically, [`indentation()`](AsTree::indentation()) sets the 429 | /// number of horizontal characters to use for each branch of the tree. 430 | /// 431 | /// # Examples 432 | /// 433 | /// ``` 434 | /// use display_tree::{AsTree, DisplayTree, StyleBuilder}; 435 | /// 436 | /// #[derive(DisplayTree)] 437 | /// struct Tree { 438 | /// a: i32, 439 | /// b: i32, 440 | /// } 441 | /// 442 | /// let tree = Tree { a: 1, b: 2 }; 443 | /// 444 | /// assert_eq!( 445 | /// format!("{}", AsTree::new(&tree).indentation(4),), 446 | /// "Tree\n\ 447 | /// ├──── 1\n\ 448 | /// └──── 2" 449 | /// ); 450 | /// ``` 451 | fn indentation(mut self, indentation: u32) -> Self { 452 | self.style_mut().indentation = indentation; 453 | self 454 | } 455 | 456 | /// Sets the style of the leaves of the tree. See [`TextStyle`] for more 457 | /// information. 458 | fn leaf_style(mut self, style: TextStyle) -> Self { 459 | self.style_mut().leaf_style = style; 460 | self 461 | } 462 | 463 | /// Sets the style of the branches of the tre. See [`TextStyle`] for more 464 | /// information. 465 | fn branch_style(mut self, style: TextStyle) -> Self { 466 | self.style_mut().branch_style = style; 467 | self 468 | } 469 | 470 | /// Sets the color of the leaves of the tree. See [`Color`] for more 471 | /// information. 472 | fn leaf_color(mut self, color: Color) -> Self { 473 | self.style_mut().leaf_style.text_color = Some(color); 474 | self 475 | } 476 | 477 | /// Sets the background color of the leaves of the tree. See [`Color`] for 478 | /// more information. 479 | fn leaf_background_color(mut self, color: Color) -> Self { 480 | self.style_mut().leaf_style.background_color = Some(color); 481 | self 482 | } 483 | 484 | /// Renders the leaves as bold. 485 | fn bold_leaves(mut self) -> Self { 486 | self.style_mut().leaf_style.is_bold = true; 487 | self 488 | } 489 | 490 | /// Decreases the intensity of the leaves. 491 | fn faint_leaves(mut self) -> Self { 492 | self.style_mut().leaf_style.is_faint = true; 493 | self 494 | } 495 | 496 | /// Italicises the leaves. 497 | fn italic_leaves(mut self) -> Self { 498 | self.style_mut().leaf_style.is_italic = true; 499 | self 500 | } 501 | 502 | /// Underlines the leaves. 503 | fn underlined_leaves(mut self) -> Self { 504 | self.style_mut().leaf_style.is_underlined = true; 505 | self 506 | } 507 | 508 | /// Causes the leaves to be crossed-out. 509 | fn strikethrough_leaves(mut self) -> Self { 510 | self.style_mut().leaf_style.is_strikethrough = true; 511 | self 512 | } 513 | 514 | /// Sets the color of the branches of the tree. See [`Color`] for more 515 | /// information. 516 | fn branch_color(mut self, color: Color) -> Self { 517 | self.style_mut().branch_style.text_color = Some(color); 518 | self 519 | } 520 | 521 | /// Sets the background color of the branches of the tree. See [`Color`] for 522 | /// more information. 523 | fn branch_background_color(mut self, color: Color) -> Self { 524 | self.style_mut().branch_style.background_color = Some(color); 525 | self 526 | } 527 | 528 | /// Renders the branches as bold. 529 | fn bold_branches(mut self) -> Self { 530 | self.style_mut().branch_style.is_bold = true; 531 | self 532 | } 533 | 534 | /// Decreases the intensity of the branches. 535 | fn faint_branches(mut self) -> Self { 536 | self.style_mut().branch_style.is_faint = true; 537 | self 538 | } 539 | } 540 | 541 | #[cfg(test)] 542 | mod tests { 543 | use super::*; 544 | 545 | #[test] 546 | fn plain() { 547 | let style = TextStyle::default(); 548 | assert_eq!(style.apply("text"), "text") 549 | } 550 | 551 | #[test] 552 | fn text_color() { 553 | let style = TextStyle { 554 | text_color: Some(Color::Red), 555 | ..TextStyle::default() 556 | }; 557 | assert_eq!(style.apply("text"), "\x1b[31mtext\x1b[0m") 558 | } 559 | 560 | #[test] 561 | fn background_color() { 562 | let style = TextStyle { 563 | background_color: Some(Color::Red), 564 | ..TextStyle::default() 565 | }; 566 | assert_eq!(style.apply("text"), "\x1b[41mtext\x1b[0m") 567 | } 568 | 569 | #[test] 570 | fn bold() { 571 | let style = TextStyle { 572 | is_bold: true, 573 | ..TextStyle::default() 574 | }; 575 | assert_eq!(style.apply("text"), "\x1b[1mtext\x1b[0m") 576 | } 577 | 578 | #[test] 579 | fn faint() { 580 | let style = TextStyle { 581 | is_faint: true, 582 | ..TextStyle::default() 583 | }; 584 | assert_eq!(style.apply("text"), "\x1b[2mtext\x1b[0m") 585 | } 586 | 587 | #[test] 588 | fn italic() { 589 | let style = TextStyle { 590 | is_italic: true, 591 | ..TextStyle::default() 592 | }; 593 | assert_eq!(style.apply("text"), "\x1b[3mtext\x1b[0m") 594 | } 595 | 596 | #[test] 597 | fn underline() { 598 | let style = TextStyle { 599 | is_underlined: true, 600 | ..TextStyle::default() 601 | }; 602 | assert_eq!(style.apply("text"), "\x1b[4mtext\x1b[0m") 603 | } 604 | 605 | #[test] 606 | fn strikethrough() { 607 | let style = TextStyle { 608 | is_strikethrough: true, 609 | ..TextStyle::default() 610 | }; 611 | assert_eq!(style.apply("text"), "\x1b[9mtext\x1b[0m") 612 | } 613 | } 614 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! [`display_tree`] provides simple, automatic, and customizable tree 2 | //! pretty-printing. 3 | //! 4 | //! This crate provies the [`DisplayTree`] trait and a macro to derive it for 5 | //! `struct`s and `enum`s. The derived implementation can be heavily customized 6 | //! using helper attributes discussed in the [`DisplayTree` 7 | //! documentation](DisplayTree) It also provides [`AsTree`] and a set of macros 8 | //! mirroring standard library counterparts for displaying or formatting tree 9 | //! types. The way a tree is formatted can be customized with the [`Style`] 10 | //! type, or builder methods on [`AsTree`], using the [`StyleBuilder`] trait. 11 | //! 12 | //! See the [`DisplayTree` documentation](DisplayTree) to learn how to make a 13 | //! type printable as a tree, or [`AsTree`] or any of the [macros] provided by 14 | //! [`display_tree`] for information on displaying or formatting your new tree 15 | //! type. 16 | //! 17 | //! [macros]: https://docs.rs/display_tree/*/display_tree/#macros 18 | //! 19 | //! # Examples 20 | //! 21 | //! ``` 22 | //! use display_tree::{format_tree, CharSet, DisplayTree, Style, StyleBuilder}; 23 | //! 24 | //! // A tree representing a numerical expression. 25 | //! #[derive(DisplayTree)] 26 | //! enum Expr { 27 | //! Int(i32), 28 | //! BinOp { 29 | //! #[node_label] 30 | //! op: char, 31 | //! #[tree] 32 | //! left: Box, 33 | //! #[tree] 34 | //! right: Box, 35 | //! }, 36 | //! UnaryOp { 37 | //! #[node_label] 38 | //! op: char, 39 | //! #[tree] 40 | //! arg: Box, 41 | //! }, 42 | //! } 43 | //! 44 | //! let expr: Expr = Expr::BinOp { 45 | //! op: '+', 46 | //! left: Box::new(Expr::UnaryOp { 47 | //! op: '-', 48 | //! arg: Box::new(Expr::Int(2)), 49 | //! }), 50 | //! right: Box::new(Expr::Int(7)), 51 | //! }; 52 | //! 53 | //! assert_eq!( 54 | //! format_tree!( 55 | //! expr, 56 | //! Style::default() 57 | //! .indentation(1) 58 | //! .char_set(CharSet::DOUBLE_LINE) 59 | //! ), 60 | //! concat!( 61 | //! "+\n", 62 | //! "╠═ -\n", 63 | //! "║ ╚═ Int\n", 64 | //! "║ ╚═ 2\n", 65 | //! "╚═ Int\n", 66 | //! " ╚═ 7", 67 | //! ), 68 | //! ); 69 | //! ``` 70 | 71 | #![warn(missing_docs)] 72 | #![deny(rustdoc::broken_intra_doc_links)] 73 | 74 | extern crate self as display_tree; 75 | 76 | mod display; 77 | pub mod to_display_tree_ref; 78 | 79 | use std::fmt; 80 | 81 | pub use display::*; 82 | pub use display_tree_derive::DisplayTree; 83 | 84 | /// A type that can be pretty-printed as a tree with a specified style. 85 | /// 86 | /// [`DisplayTree`] can be derived for `struct`s and `enum`s, and generally the 87 | /// derived implementation should be sufficient, but it can be manually 88 | /// implemented if needed. 89 | /// 90 | /// # Deriving 91 | /// 92 | /// Deriving [`DisplayTree`] for a type requires that all of its fields not 93 | /// marked `#[tree]` implement [`std::fmt::Display`]. A derived implementation 94 | /// will be formatted as the name of the `struct` or variant as a node, followed 95 | /// by a branches underneath with a node for each field. 96 | /// 97 | /// The [`AsTree`] type should be used to display a tree implementing 98 | /// [`DisplayTree`]. See the [`AsTree` documentation](AsTree) for more 99 | /// information. 100 | /// 101 | /// ``` 102 | /// use display_tree::{format_tree, AsTree, DisplayTree}; 103 | /// 104 | /// // A tree representing a numerical expression. 105 | /// #[derive(DisplayTree)] 106 | /// enum Expr { 107 | /// Int(i32), 108 | /// BinOp { 109 | /// #[node_label] 110 | /// op: char, 111 | /// #[tree] 112 | /// left: Box, 113 | /// #[tree] 114 | /// right: Box, 115 | /// }, 116 | /// UnaryOp { 117 | /// #[node_label] 118 | /// op: char, 119 | /// #[tree] 120 | /// arg: Box, 121 | /// }, 122 | /// } 123 | /// 124 | /// # fn get_expr() -> Expr { 125 | /// # Expr::BinOp { 126 | /// # op: '+', 127 | /// # left: Box::new(Expr::UnaryOp { 128 | /// # op: '-', 129 | /// # arg: Box::new(Expr::Int(2)) 130 | /// # }), 131 | /// # right: Box::new(Expr::Int(7)) 132 | /// # } 133 | /// # } 134 | /// let expr: Expr = get_expr(); 135 | /// 136 | /// assert_eq!( 137 | /// format_tree!(expr), 138 | /// concat!( 139 | /// "+\n", 140 | /// "├── -\n", 141 | /// "│ └── Int\n", 142 | /// "│ └── 2\n", 143 | /// "└── Int\n", 144 | /// " └── 7", 145 | /// ), 146 | /// ); 147 | /// ``` 148 | /// 149 | /// ## Helper Attributes 150 | /// 151 | /// [`derive(DisplayTree)`] provies a few helper attribute that allow the 152 | /// derived implementation to be customized. 153 | /// 154 | /// ### Field Attributes 155 | /// 156 | /// - `#[tree]` marks a field that should be formatted as a tree. By default, a 157 | /// field's [`std::fmt::Display`] implementation will be used to format it in 158 | /// the tree, but fields can be marked with `#[tree]` to use their 159 | /// [`DisplayTree`] implementation instead. `#[tree]` can be used on any type 160 | /// that conforms to 161 | /// [`ToDisplayTreeRef`](to_display_tree_ref::ToDisplayTreeRef), which 162 | /// includes any types that conform to [`DisplayTree`]. 163 | /// 164 | /// - `#[ignore_field]` marks a field that should not be included in the tree. 165 | /// When the tree is formatted, the field will not be present. 166 | /// 167 | /// - `#[node_label]` causes a field to be used as the label of the node of the 168 | /// tree that it is under. By default, the name of the `struct` or variant 169 | /// will be used as the label. For example, for a variant representing a 170 | /// binary operator and its arguments, you might want the operator to be the 171 | /// used as the label of the tree. 172 | /// 173 | /// - `#[field_label (= "label")]` causes the node for a field to have a label 174 | /// in the form `label: value` when it is formatted. A string literal can be 175 | /// passed to specify the label, otherwise the name of the field will be used. 176 | /// 177 | /// ### Struct/Variant Attributes 178 | /// 179 | /// - `#[node_label = "label"]` specifies the label to use for the node of the 180 | /// tree. By default, the name of the `struct` or variant will be used. 181 | /// 182 | /// # Examples 183 | /// 184 | /// Specifying a field label: 185 | /// 186 | /// ``` 187 | /// #[derive(display_tree::DisplayTree)] 188 | /// struct Point { 189 | /// #[field_label] 190 | /// x: i32, 191 | /// #[field_label] 192 | /// y: i32, 193 | /// } 194 | /// ``` 195 | /// 196 | /// Ignoring a field: 197 | /// 198 | /// ``` 199 | /// #[derive(display_tree::DisplayTree)] 200 | /// struct Numbers { 201 | /// not_so_secret_number: i32, 202 | /// // `super_secret_number` not included when tree is formatted. 203 | /// #[ignore_field] 204 | /// super_secret_number: i32, 205 | /// } 206 | /// ``` 207 | /// 208 | /// Using a field as the node label: 209 | /// 210 | /// ``` 211 | /// #[derive(display_tree::DisplayTree)] 212 | /// enum Expr { 213 | /// Num(i32), 214 | /// BinOp { 215 | /// // Show the operator as the node of this variant. 216 | /// #[node_label] 217 | /// op: char, 218 | /// #[tree] 219 | /// left: Box, 220 | /// #[tree] 221 | /// right: Box, 222 | /// }, 223 | /// } 224 | /// ``` 225 | /// 226 | /// Using a custom node label: 227 | /// 228 | /// ``` 229 | /// #[derive(display_tree::DisplayTree)] 230 | /// // Use "MyStruct" as the node label instead of the name of the `struct`. 231 | /// #[node_label = "MyStruct"] 232 | /// struct MyVeryLongComplexDetailedImportantStruct(bool); 233 | /// ``` 234 | pub trait DisplayTree { 235 | /// Formats the tree using the given formatter and the given style. 236 | /// 237 | /// [`fmt()`](DisplayTree::fmt()) should not be called directly. It is used 238 | /// by [`AsTree`] to format a tree. 239 | /// 240 | /// # Examples 241 | /// 242 | /// ``` 243 | /// use display_tree::{AsTree, DisplayTree, Style}; 244 | /// 245 | /// struct Point { 246 | /// x: i32, 247 | /// y: i32, 248 | /// } 249 | /// 250 | /// impl DisplayTree for Point { 251 | /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>, style: Style) -> std::fmt::Result { 252 | /// writeln!(f, "Point")?; 253 | /// writeln!( 254 | /// f, 255 | /// "{}{} x: {}", 256 | /// style.char_set.connector, 257 | /// std::iter::repeat(style.char_set.horizontal) 258 | /// .take(style.indentation as usize) 259 | /// .collect::(), 260 | /// self.x 261 | /// )?; 262 | /// write!( 263 | /// f, 264 | /// "{}{} y: {}", 265 | /// style.char_set.end_connector, 266 | /// std::iter::repeat(style.char_set.horizontal) 267 | /// .take(style.indentation as usize) 268 | /// .collect::(), 269 | /// self.y 270 | /// ) 271 | /// } 272 | /// } 273 | /// 274 | /// assert_eq!( 275 | /// format!("{}", AsTree::new(&Point { x: 10, y: 20 })), 276 | /// "Point\n\ 277 | /// ├── x: 10\n\ 278 | /// └── y: 20", 279 | /// ); 280 | /// ``` 281 | fn fmt(&self, f: &mut fmt::Formatter, style: Style) -> fmt::Result; 282 | } 283 | 284 | /// Prints a type that implements [`DisplayTree`] to the standard output as a 285 | /// tree. 286 | /// 287 | /// A [`Style`] can be passed as the second argument to customize the way the 288 | /// tree is formatted. 289 | /// 290 | /// # Examples 291 | /// 292 | /// ``` 293 | /// # #[derive(display_tree::DisplayTree)] 294 | /// # struct Tree; 295 | /// # let tree = Tree; 296 | /// use display_tree::print_tree; 297 | /// print_tree!(tree); 298 | /// ``` 299 | /// 300 | /// Specifying a style: 301 | /// 302 | /// ``` 303 | /// # #[derive(display_tree::DisplayTree)] 304 | /// # struct Tree; 305 | /// # let tree = Tree; 306 | /// use display_tree::{print_tree, Style, StyleBuilder}; 307 | /// print_tree!(tree, Style::default().indentation(1)); 308 | /// ``` 309 | #[macro_export] 310 | macro_rules! print_tree { 311 | ($tree:expr $(,)?) => { 312 | ::std::print!("{}", $crate::AsTree::new(&$tree)) 313 | }; 314 | ($tree:expr, $style:expr $(,)?) => { 315 | ::std::print!("{}", $crate::AsTree::with_style(&$tree, $style)) 316 | }; 317 | } 318 | 319 | /// Prints a type that implements [`DisplayTree`] to the standard output as a 320 | /// tree, with a newline. 321 | /// 322 | /// A [`Style`] can be passed as the second argument to customize the way the 323 | /// tree is formatted. 324 | /// 325 | /// # Examples 326 | /// 327 | /// ``` 328 | /// # #[derive(display_tree::DisplayTree)] 329 | /// # struct Tree; 330 | /// # let tree = Tree; 331 | /// use display_tree::println_tree; 332 | /// println_tree!(tree) 333 | /// ``` 334 | /// 335 | /// Specifying a style: 336 | /// 337 | /// ``` 338 | /// # #[derive(display_tree::DisplayTree)] 339 | /// # struct Tree; 340 | /// # let tree = Tree; 341 | /// use display_tree::{println_tree, Style, StyleBuilder}; 342 | /// println_tree!(tree, Style::default().indentation(1)); 343 | /// ``` 344 | #[macro_export] 345 | macro_rules! println_tree { 346 | ($tree:expr $(,)?) => { 347 | ::std::println!("{}", $crate::AsTree::new(&$tree)) 348 | }; 349 | ($tree:expr, $style:expr $(,)?) => { 350 | ::std::println!("{}", $crate::AsTree::with_style(&$tree, $style)) 351 | }; 352 | } 353 | 354 | /// Writes a type that implements [`DisplayTree`] to a buffer as a tree. 355 | /// 356 | /// A [`Style`] can be passed as the second argument to customize the way the 357 | /// tree is formatted. 358 | /// 359 | /// # Examples 360 | /// 361 | /// ``` 362 | /// # use std::io::Write; 363 | /// use display_tree::write_tree; 364 | /// 365 | /// #[derive(display_tree::DisplayTree)] 366 | /// struct Tree; 367 | /// 368 | /// let mut buf = Vec::new(); 369 | /// write_tree!(&mut buf, Tree); 370 | /// 371 | /// assert_eq!(&buf, "Tree".as_bytes()); 372 | /// ``` 373 | /// 374 | /// Specifying a style: 375 | /// 376 | /// ``` 377 | /// # use std::io::Write; 378 | /// use display_tree::{write_tree, CharSet, Style, StyleBuilder}; 379 | /// 380 | /// #[derive(display_tree::DisplayTree)] 381 | /// struct Tree { 382 | /// a: i32, 383 | /// b: bool, 384 | /// } 385 | /// 386 | /// let mut buf = Vec::new(); 387 | /// write_tree!( 388 | /// &mut buf, 389 | /// Tree { a: 1, b: true }, 390 | /// Style::default().char_set(CharSet::SINGLE_LINE_CURVED) 391 | /// ); 392 | /// 393 | /// assert_eq!( 394 | /// &buf, 395 | /// "Tree\n\ 396 | /// ├── 1\n\ 397 | /// ╰── true" 398 | /// .as_bytes() 399 | /// ); 400 | /// ``` 401 | #[macro_export] 402 | macro_rules! write_tree { 403 | ($f:expr, $tree:expr $(,)?) => { 404 | ::std::write!($f, "{}", $crate::AsTree::new(&$tree)) 405 | }; 406 | ($f:expr, $tree:expr, $style:expr $(,)?) => { 407 | ::std::write!($f, "{}", $crate::AsTree::with_style(&$tree, $style)) 408 | }; 409 | } 410 | 411 | /// Writes a type that implements [`DisplayTree`] to a buffer as a tree, with a 412 | /// newline. 413 | /// 414 | /// A [`Style`] can be passed as the second argument to customize the way the 415 | /// tree is formatted. 416 | /// 417 | /// # Examples 418 | /// 419 | /// ``` 420 | /// # use std::io::Write; 421 | /// use display_tree::writeln_tree; 422 | /// 423 | /// #[derive(display_tree::DisplayTree)] 424 | /// struct Tree; 425 | /// 426 | /// let mut buf = Vec::new(); 427 | /// writeln_tree!(&mut buf, Tree); 428 | /// 429 | /// assert_eq!(&buf, "Tree\n".as_bytes()); 430 | /// ``` 431 | /// 432 | /// Specifying a style: 433 | /// 434 | /// ``` 435 | /// # use std::io::Write; 436 | /// use display_tree::{writeln_tree, CharSet, Style, StyleBuilder}; 437 | /// 438 | /// #[derive(display_tree::DisplayTree)] 439 | /// struct Tree { 440 | /// a: i32, 441 | /// b: bool, 442 | /// } 443 | /// 444 | /// let mut buf = Vec::new(); 445 | /// writeln_tree!( 446 | /// &mut buf, 447 | /// Tree { a: 1, b: true }, 448 | /// Style::default().char_set(CharSet::SINGLE_LINE_BOLD) 449 | /// ); 450 | /// 451 | /// assert_eq!( 452 | /// &buf, 453 | /// "Tree\n\ 454 | /// ┣━━ 1\n\ 455 | /// ┗━━ true\n" 456 | /// .as_bytes() 457 | /// ); 458 | /// ``` 459 | #[macro_export] 460 | macro_rules! writeln_tree { 461 | ($f:expr, $tree:expr $(,)?) => { 462 | ::std::writeln!($f, "{}", $crate::AsTree::new(&$tree)) 463 | }; 464 | ($f:expr, $tree:expr, $style:expr $(,)?) => { 465 | ::std::writeln!($f, "{}", $crate::AsTree::with_style(&$tree, $style)) 466 | }; 467 | } 468 | 469 | /// Creates a [`String`] from a type that implements [`DisplayTree`], formatting 470 | /// it as a tree. 471 | /// 472 | /// A [`Style`] can be passed as the second argument to customize the way the 473 | /// tree is formatted. 474 | /// 475 | /// # Examples 476 | /// 477 | /// ``` 478 | /// use display_tree::format_tree; 479 | /// 480 | /// #[derive(display_tree::DisplayTree)] 481 | /// struct Tree; 482 | /// 483 | /// assert_eq!(format_tree!(Tree), "Tree") 484 | /// ``` 485 | /// 486 | /// Specifying a style: 487 | /// 488 | /// ``` 489 | /// use display_tree::{format_tree, Style, StyleBuilder}; 490 | /// 491 | /// #[derive(display_tree::DisplayTree)] 492 | /// struct Tree { 493 | /// a: i32, 494 | /// b: bool, 495 | /// } 496 | /// 497 | /// assert_eq!( 498 | /// format_tree!(Tree { a: 1, b: true }, Style::default().indentation(1)), 499 | /// "Tree\n\ 500 | /// ├─ 1\n\ 501 | /// └─ true" 502 | /// ); 503 | /// ``` 504 | #[macro_export] 505 | macro_rules! format_tree { 506 | ($tree:expr $(,)?) => { 507 | ::std::format!("{}", $crate::AsTree::new(&$tree)) 508 | }; 509 | ($tree:expr, $style:expr $(,)?) => { 510 | ::std::format!("{}", $crate::AsTree::with_style(&$tree, $style)) 511 | }; 512 | } 513 | 514 | #[cfg(test)] 515 | mod tests { 516 | #[test] 517 | fn write() { 518 | use std::io::Write; 519 | 520 | #[derive(display_tree::DisplayTree)] 521 | struct Tree; 522 | 523 | let mut buf = Vec::new(); 524 | display_tree::write_tree!(&mut buf, Tree).unwrap(); 525 | 526 | assert_eq!(&buf, "Tree".as_bytes()); 527 | } 528 | 529 | #[test] 530 | fn writeln() { 531 | use std::io::Write; 532 | 533 | #[derive(display_tree::DisplayTree)] 534 | struct Tree; 535 | 536 | let mut buf = Vec::new(); 537 | display_tree::writeln_tree!(&mut buf, Tree).unwrap(); 538 | 539 | assert_eq!(&buf, "Tree\n".as_bytes()); 540 | } 541 | 542 | #[test] 543 | fn format() { 544 | #[derive(display_tree::DisplayTree)] 545 | struct Tree; 546 | 547 | assert_eq!(display_tree::format_tree!(Tree), "Tree") 548 | } 549 | } 550 | -------------------------------------------------------------------------------- /src/to_display_tree_ref.rs: -------------------------------------------------------------------------------- 1 | //! A module containing the [`ToDisplayTreeRef`] trait and implementations for 2 | //! [`std`] types. 3 | 4 | use std::ops::Deref; 5 | 6 | use super::DisplayTree; 7 | 8 | /// A type that can be converted into a reference to a type that implements 9 | /// [`DisplayTree`]. 10 | /// 11 | /// [`ToDisplayTreeRef`] is used to format fields in with a derived 12 | /// [`DisplayTree`] implementation annotated with `#[tree]`. For example, it is 13 | /// implemented for [`Box`] so that a boxed field can be 14 | /// formatted as a tree. 15 | /// 16 | /// [`ToDisplayTreeRef`] should generally not implemented for any new types, 17 | /// unless you run into an edge case that is not covered by the implementations 18 | /// provided by [`display_tree`]. 19 | pub trait ToDisplayTreeRef { 20 | /// Converts this type into a type which implements [`DisplayTree`]. 21 | /// 22 | /// [`to_display_tree()`](ToDisplayTreeRef::to_display_tree()) should not 23 | /// be called directly. It is used by [`display_tree_derive`] in the 24 | /// code emitted by 25 | /// [`derive(DisplayTree)`]. 26 | fn to_display_tree(&self) -> &T; 27 | } 28 | 29 | impl ToDisplayTreeRef for T { 30 | fn to_display_tree(&self) -> &T { 31 | self 32 | } 33 | } 34 | 35 | impl ToDisplayTreeRef for &T { 36 | fn to_display_tree(&self) -> &T { 37 | self 38 | } 39 | } 40 | 41 | impl ToDisplayTreeRef for Box { 42 | fn to_display_tree(&self) -> &T { 43 | Deref::deref(self) 44 | } 45 | } 46 | 47 | impl ToDisplayTreeRef for std::rc::Rc { 48 | fn to_display_tree(&self) -> &T { 49 | Deref::deref(self) 50 | } 51 | } 52 | 53 | impl ToDisplayTreeRef for std::sync::Arc { 54 | fn to_display_tree(&self) -> &T { 55 | Deref::deref(self) 56 | } 57 | } 58 | 59 | impl<'a, T: DisplayTree + Clone> ToDisplayTreeRef for std::borrow::Cow<'a, T> { 60 | fn to_display_tree(&self) -> &T { 61 | Deref::deref(self) 62 | } 63 | } 64 | 65 | #[cfg(test)] 66 | mod tests { 67 | use super::*; 68 | 69 | #[test] 70 | fn from_value() { 71 | #[derive(DisplayTree, PartialEq, Debug)] 72 | struct Tree; 73 | 74 | assert_eq!(Tree.to_display_tree(), &Tree) 75 | } 76 | 77 | #[test] 78 | fn from_reference() { 79 | #[derive(DisplayTree, PartialEq, Debug)] 80 | struct Tree; 81 | 82 | let reference = &Tree; 83 | assert_eq!(reference.to_display_tree(), &Tree) 84 | } 85 | 86 | #[test] 87 | fn from_box() { 88 | #[derive(DisplayTree, PartialEq, Debug)] 89 | struct Tree; 90 | 91 | assert_eq!(Box::new(Tree).to_display_tree(), &Tree) 92 | } 93 | 94 | #[test] 95 | fn from_rc() { 96 | #[derive(DisplayTree, PartialEq, Debug)] 97 | struct Tree; 98 | 99 | assert_eq!(std::rc::Rc::new(Tree).to_display_tree(), &Tree) 100 | } 101 | 102 | #[test] 103 | fn from_arc() { 104 | #[derive(DisplayTree, PartialEq, Debug)] 105 | struct Tree; 106 | 107 | assert_eq!(std::sync::Arc::new(Tree).to_display_tree(), &Tree) 108 | } 109 | 110 | #[test] 111 | fn from_cow() { 112 | #[derive(DisplayTree, PartialEq, Debug, Clone)] 113 | struct Tree; 114 | 115 | let cow: std::borrow::Cow = std::borrow::Cow::Owned(Tree); 116 | assert_eq!(cow.to_display_tree(), &Tree) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /tests/attrs.rs: -------------------------------------------------------------------------------- 1 | use display_tree::*; 2 | 3 | #[test] 4 | fn node_attr_node_label_struct() { 5 | #[derive(DisplayTree)] 6 | #[node_label = "label"] 7 | struct Tree { 8 | a: i32, 9 | b: bool, 10 | } 11 | 12 | let tree = Tree { a: 1, b: true }; 13 | assert_eq!( 14 | format!("{}", AsTree::new(&tree)), 15 | "label\n\ 16 | ├── 1\n\ 17 | └── true", 18 | ); 19 | } 20 | 21 | #[test] 22 | fn node_attr_node_label_enum() { 23 | #[derive(DisplayTree)] 24 | enum Tree { 25 | #[node_label = "label"] 26 | A { a: i32, b: bool }, 27 | } 28 | 29 | let tree = Tree::A { a: 1, b: true }; 30 | assert_eq!( 31 | format!("{}", AsTree::new(&tree)), 32 | "label\n\ 33 | ├── 1\n\ 34 | └── true", 35 | ); 36 | } 37 | 38 | #[test] 39 | fn field_attr_field_label_no_args_struct() { 40 | #[derive(DisplayTree)] 41 | struct Tree { 42 | #[field_label] 43 | a: i32, 44 | b: bool, 45 | } 46 | 47 | let tree = Tree { a: 1, b: true }; 48 | assert_eq!( 49 | format!("{}", AsTree::new(&tree)), 50 | "Tree\n\ 51 | ├── a: 1\n\ 52 | └── true", 53 | ); 54 | } 55 | 56 | #[test] 57 | fn field_attr_field_label_struct() { 58 | #[derive(DisplayTree)] 59 | struct Tree { 60 | #[field_label = "label"] 61 | a: i32, 62 | b: bool, 63 | } 64 | 65 | let tree = Tree { a: 1, b: true }; 66 | assert_eq!( 67 | format!("{}", AsTree::new(&tree)), 68 | "Tree\n\ 69 | ├── label: 1\n\ 70 | └── true", 71 | ); 72 | } 73 | 74 | #[test] 75 | fn field_attr_field_label_no_args_enum() { 76 | #[derive(DisplayTree)] 77 | enum Tree { 78 | A { 79 | #[field_label] 80 | a: i32, 81 | b: bool, 82 | }, 83 | } 84 | 85 | let tree = Tree::A { a: 1, b: true }; 86 | assert_eq!( 87 | format!("{}", AsTree::new(&tree)), 88 | "A\n\ 89 | ├── a: 1\n\ 90 | └── true", 91 | ); 92 | } 93 | 94 | #[test] 95 | fn field_attr_field_label_enum() { 96 | #[derive(DisplayTree)] 97 | enum Tree { 98 | A { 99 | #[field_label = "label"] 100 | a: i32, 101 | b: bool, 102 | }, 103 | } 104 | 105 | let tree = Tree::A { a: 1, b: true }; 106 | assert_eq!( 107 | format!("{}", AsTree::new(&tree)), 108 | "A\n\ 109 | ├── label: 1\n\ 110 | └── true", 111 | ); 112 | } 113 | 114 | #[allow(dead_code)] 115 | #[test] 116 | fn field_attr_ignore_field_struct() { 117 | #[derive(DisplayTree)] 118 | struct Tree { 119 | a: i32, 120 | #[ignore_field] 121 | b: bool, 122 | } 123 | 124 | let tree = Tree { a: 1, b: true }; 125 | assert_eq!( 126 | format!("{}", AsTree::new(&tree)), 127 | "Tree\n\ 128 | └── 1", 129 | ); 130 | } 131 | 132 | #[allow(unused_variables)] 133 | #[test] 134 | fn field_attr_ignore_field_enum() { 135 | #[derive(DisplayTree)] 136 | enum Tree { 137 | A { 138 | a: i32, 139 | #[ignore_field] 140 | b: bool, 141 | }, 142 | } 143 | 144 | let tree = Tree::A { a: 1, b: true }; 145 | assert_eq!( 146 | format!("{}", AsTree::new(&tree)), 147 | "A\n\ 148 | └── 1", 149 | ); 150 | } 151 | 152 | #[test] 153 | fn field_attr_node_label_struct() { 154 | #[derive(DisplayTree)] 155 | struct Tree { 156 | #[node_label] 157 | a: i32, 158 | b: bool, 159 | } 160 | 161 | let tree = Tree { a: 1, b: true }; 162 | assert_eq!( 163 | format!("{}", AsTree::new(&tree)), 164 | "1\n\ 165 | └── true", 166 | ); 167 | } 168 | 169 | #[test] 170 | fn field_attr_node_label_enum() { 171 | #[derive(DisplayTree)] 172 | enum Tree { 173 | A { 174 | #[node_label] 175 | a: i32, 176 | b: bool, 177 | }, 178 | } 179 | 180 | let tree = Tree::A { a: 1, b: true }; 181 | assert_eq!( 182 | format!("{}", AsTree::new(&tree)), 183 | "1\n\ 184 | └── true", 185 | ); 186 | } 187 | 188 | #[test] 189 | fn field_attr_tree_struct() { 190 | #[derive(DisplayTree)] 191 | struct Tree { 192 | a: i32, 193 | #[tree] 194 | b: Inner, 195 | } 196 | 197 | #[derive(DisplayTree)] 198 | struct Inner { 199 | a: i32, 200 | b: bool, 201 | } 202 | 203 | let tree = Tree { 204 | a: 1, 205 | b: Inner { a: 2, b: true }, 206 | }; 207 | #[rustfmt::skip] 208 | assert_eq!( 209 | format!("{}", AsTree::new(&tree)), 210 | concat!( 211 | "Tree\n", 212 | "├── 1\n", 213 | "└── Inner\n", 214 | " ├── 2\n", 215 | " └── true", 216 | ), 217 | ); 218 | } 219 | 220 | #[test] 221 | fn field_attr_tree_enum() { 222 | #[derive(DisplayTree)] 223 | enum Tree { 224 | A(#[tree] Inner), 225 | } 226 | 227 | #[derive(DisplayTree)] 228 | struct Inner { 229 | a: i32, 230 | b: bool, 231 | } 232 | 233 | let tree = Tree::A(Inner { a: 1, b: true }); 234 | #[rustfmt::skip] 235 | assert_eq!( 236 | format!("{}", AsTree::new(&tree)), 237 | concat!( 238 | "A\n", 239 | "└── Inner\n", 240 | " ├── 1\n", 241 | " └── true" 242 | ), 243 | ); 244 | } 245 | 246 | // #[test] 247 | // fn field_attr_tree_ref() { 248 | // #[derive(DisplayTree)] 249 | // enum Tree<'a> { 250 | // A { a: i32, b: bool }, 251 | // B(#[tree] &'a Self), 252 | // } 253 | 254 | // let tree = Tree::B(&Tree::A { a: 1, b: true }); 255 | // assert_eq!( 256 | // format!("{}", AsTree::new(&tree)), 257 | // #[rustfmt::skip] 258 | // concat!( 259 | // "B\n", 260 | // "└── A\n", 261 | // " ├── 1\n", 262 | // " └── true", 263 | // ), 264 | // ); 265 | // } 266 | 267 | #[test] 268 | fn field_attr_tree_self() { 269 | #[derive(DisplayTree)] 270 | enum Tree { 271 | A { a: i32, b: bool }, 272 | B(#[tree] Box), 273 | } 274 | 275 | let tree = Tree::B(Box::new(Tree::A { a: 1, b: true })); 276 | #[rustfmt::skip] 277 | assert_eq!( 278 | format!("{}", AsTree::new(&tree)), 279 | concat!( 280 | "B\n", 281 | "└── A\n", 282 | " ├── 1\n", 283 | " └── true", 284 | ), 285 | ); 286 | } 287 | 288 | // #[test] 289 | // fn field_attr_tree_rc() { 290 | // use std::rc::Rc; 291 | 292 | // #[derive(DisplayTree)] 293 | // enum Tree { 294 | // A { a: i32, b: bool }, 295 | // B(#[tree] Rc), 296 | // } 297 | 298 | // let tree = Tree::B(Rc::new(Tree::A { a: 1, b: true })); 299 | // assert_eq!( 300 | // format!("{}", AsTree::new(&tree)), 301 | // indoc! {" 302 | // B 303 | // └── A 304 | // ├── 1 305 | // └── true" 306 | // } 307 | // ); 308 | // } 309 | 310 | // #[test] 311 | // fn field_attr_tree_arc() { 312 | // use std::sync::Arc; 313 | 314 | // #[derive(DisplayTree)] 315 | // enum Tree { 316 | // A { a: i32, b: bool }, 317 | // B(#[tree] Arc), 318 | // } 319 | 320 | // let tree = Tree::B(Arc::new(Tree::A { a: 1, b: true })); 321 | // assert_eq!( 322 | // format!("{}", AsTree::new(&tree)), 323 | // indoc! {" 324 | // B 325 | // └── A 326 | // ├── 1 327 | // └── true" 328 | // } 329 | // ); 330 | // } 331 | -------------------------------------------------------------------------------- /tests/derive.rs: -------------------------------------------------------------------------------- 1 | use display_tree::*; 2 | 3 | #[test] 4 | fn field_struct() { 5 | #[derive(DisplayTree)] 6 | struct Tree { 7 | a: i32, 8 | b: bool, 9 | } 10 | 11 | let tree = Tree { a: 1, b: true }; 12 | assert_eq!( 13 | format!("{}", AsTree::new(&tree)), 14 | "Tree\n\ 15 | ├── 1\n\ 16 | └── true", 17 | ); 18 | } 19 | 20 | #[test] 21 | fn tuple_struct() { 22 | #[derive(DisplayTree)] 23 | struct Tree(i32, bool); 24 | 25 | let tree = Tree(1, true); 26 | assert_eq!( 27 | format!("{}", AsTree::new(&tree)), 28 | "Tree\n\ 29 | ├── 1\n\ 30 | └── true", 31 | ); 32 | } 33 | 34 | #[test] 35 | fn enum_unit() { 36 | #[derive(DisplayTree)] 37 | enum Tree { 38 | A, 39 | } 40 | 41 | let tree = Tree::A; 42 | assert_eq!(format!("{}", AsTree::new(&tree)), "A"); 43 | } 44 | 45 | #[test] 46 | fn enum_unnamed() { 47 | #[derive(DisplayTree)] 48 | enum Tree { 49 | A(i32, bool), 50 | } 51 | 52 | let tree = Tree::A(1, true); 53 | assert_eq!( 54 | format!("{}", AsTree::new(&tree)), 55 | "A\n\ 56 | ├── 1\n\ 57 | └── true", 58 | ); 59 | } 60 | 61 | #[test] 62 | fn enum_named() { 63 | #[derive(DisplayTree)] 64 | enum Tree { 65 | A { a: i32, b: bool }, 66 | } 67 | 68 | let tree = Tree::A { a: 1, b: true }; 69 | assert_eq!( 70 | format!("{}", AsTree::new(&tree)), 71 | "A\n\ 72 | ├── 1\n\ 73 | └── true", 74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /tests/style.rs: -------------------------------------------------------------------------------- 1 | use display_tree::*; 2 | 3 | #[test] 4 | fn char_set() { 5 | #[derive(DisplayTree)] 6 | struct Tree { 7 | a: i32, 8 | b: bool, 9 | } 10 | 11 | let tree = Tree { a: 1, b: true }; 12 | assert_eq!( 13 | format!("{}", AsTree::new(&tree).char_set(CharSet::ASCII)), 14 | "Tree\n\ 15 | |-- 1\n\ 16 | `-- true", 17 | ); 18 | } 19 | 20 | #[test] 21 | fn indentation() { 22 | #[derive(DisplayTree)] 23 | struct Tree { 24 | a: i32, 25 | b: bool, 26 | } 27 | 28 | let tree = Tree { a: 1, b: true }; 29 | assert_eq!( 30 | format!("{}", AsTree::new(&tree).indentation(4)), 31 | "Tree\n\ 32 | ├──── 1\n\ 33 | └──── true", 34 | ); 35 | } 36 | --------------------------------------------------------------------------------