diff --git a/Cargo.lock b/Cargo.lock index e862271..9113388 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,267 +2,37 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "aho-corasick" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" -dependencies = [ - "memchr", -] - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "base64" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bumpalo" -version = "3.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" - -[[package]] -name = "cc" -version = "1.0.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "crc32fast" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" -dependencies = [ - "cfg-if", -] - [[package]] name = "dbg-pls" version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e845b944ea4a6b446aec7c221c48fd6e73f2ab38e1af720cac0f47895dcc4580" dependencies = [ - "dbg-pls-derive", "itoa", "once_cell", - "prettyplease", "proc-macro2", "quote", "ryu", "syn", - "syntect", - "textwrap", -] - -[[package]] -name = "dbg-pls-derive" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b46318b37603779055a193fbb0454bb762e284f72a4d3c231f4a6ab8d3eede31" -dependencies = [ - "proc-macro2", - "quote", - "syn", ] [[package]] name = "dilaria" version = "0.1.0" dependencies = [ - "bumpalo", "dbg-pls", ] -[[package]] -name = "flate2" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39522e96686d38f4bc984b9198e3a0613264abaebaff2c5c918bfa6b6da09af" -dependencies = [ - "cfg-if", - "crc32fast", - "libc", - "miniz_oxide", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" - -[[package]] -name = "indexmap" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" -dependencies = [ - "autocfg", - "hashbrown", -] - [[package]] name = "itoa" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - -[[package]] -name = "libc" -version = "0.2.124" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a41fed9d98f27ab1c6d161da622a4fa35e8a54a8adc24bbf3ddd0ef70b0e50" - -[[package]] -name = "line-wrap" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9" -dependencies = [ - "safemem", -] - -[[package]] -name = "linked-hash-map" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" - -[[package]] -name = "memchr" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" - -[[package]] -name = "miniz_oxide" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" -dependencies = [ - "adler", -] - -[[package]] -name = "num_threads" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aba1801fb138d8e85e11d0fc70baf4fe1cdfffda7c6cd34a854905df588e5ed0" -dependencies = [ - "libc", -] - [[package]] name = "once_cell" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" -[[package]] -name = "onig" -version = "6.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ddfe2c93bb389eea6e6d713306880c7f6dcc99a75b659ce145d962c861b225" -dependencies = [ - "bitflags", - "lazy_static", - "libc", - "onig_sys", -] - -[[package]] -name = "onig_sys" -version = "69.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd3eee045c84695b53b20255bb7317063df090b68e18bfac0abb6c39cf7f33e" -dependencies = [ - "cc", - "pkg-config", -] - -[[package]] -name = "pkg-config" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" - -[[package]] -name = "plist" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd39bc6cdc9355ad1dc5eeedefee696bb35c34caf21768741e81826c0bbd7225" -dependencies = [ - "base64", - "indexmap", - "line-wrap", - "serde", - "time", - "xml-rs", -] - -[[package]] -name = "prettyplease" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b83ec2d0af5c5c556257ff52c9f98934e243b9fd39604bfb2a9b75ec2e97f18" -dependencies = [ - "proc-macro2", - "syn", -] - [[package]] name = "proc-macro2" version = "1.0.37" @@ -281,78 +51,12 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "regex" -version = "1.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" - [[package]] name = "ryu" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" -[[package]] -name = "safemem" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "serde" -version = "1.0.136" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" - -[[package]] -name = "serde_derive" -version = "1.0.136" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "smawk" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" - [[package]] name = "syn" version = "1.0.91" @@ -364,124 +68,8 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "syntect" -version = "4.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b20815bbe80ee0be06e6957450a841185fcf690fe0178f14d77a05ce2caa031" -dependencies = [ - "bincode", - "bitflags", - "flate2", - "fnv", - "lazy_static", - "lazycell", - "onig", - "plist", - "regex-syntax", - "serde", - "serde_derive", - "serde_json", - "walkdir", - "yaml-rust", -] - -[[package]] -name = "textwrap" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" -dependencies = [ - "smawk", - "unicode-linebreak", - "unicode-width", -] - -[[package]] -name = "time" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" -dependencies = [ - "itoa", - "libc", - "num_threads", -] - -[[package]] -name = "unicode-linebreak" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a52dcaab0c48d931f7cc8ef826fa51690a08e1ea55117ef26f89864f532383f" -dependencies = [ - "regex", -] - -[[package]] -name = "unicode-width" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" - [[package]] name = "unicode-xid" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" - -[[package]] -name = "walkdir" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" -dependencies = [ - "same-file", - "winapi", - "winapi-util", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "xml-rs" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" - -[[package]] -name = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] diff --git a/Cargo.toml b/Cargo.toml index a90a818..bc8f9e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,5 +6,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bumpalo = { version = "3.8.0", features = ["collections"] } -dbg-pls = { version = "0.2.2", features = ["colors", "derive"] } +dbg-pls = { path = "./dbg-pls" } diff --git a/dbg-pls/.gitignore b/dbg-pls/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/dbg-pls/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/dbg-pls/Cargo.toml b/dbg-pls/Cargo.toml new file mode 100644 index 0000000..e473a06 --- /dev/null +++ b/dbg-pls/Cargo.toml @@ -0,0 +1,52 @@ +[workspace] +members = ["debug-derive"] + +[package] +name = "dbg-pls" +version = "0.2.2" +authors = ["Conrad Ludgate "] +edition = "2018" +description = "Syntax aware pretty-printing debugging" +license = "MIT" +repository = "https://github.com/conradludgate/dbg-pls" +readme = "README.md" + +include = [ + "src", + "README.md", + "assets/syntaxes/Rust/Rust.sublime-syntax", + "assets/themes/sublime-monokai-extended/Monokai Extended.tmTheme", +] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +syn = { version = "1", features = ["full"] } +proc-macro2 = "1" +quote = "1" +itoa = "1" +ryu = "1" + +# derive +dbg-pls-derive = { version = "0.2.1", path = "debug-derive", optional = true } + +# pretty +prettyplease = { version = "0.1", optional = true } +textwrap = { version = "0.15", optional = true } + +# colors +syntect = { version = "4.6.0", optional = true } +once_cell = "1" + +[dev-dependencies] +dbg-pls = { path = ".", features = ["derive", "pretty", "colors"] } + +[features] +default = [] +derive = ["dbg-pls-derive"] +pretty = ["prettyplease", "textwrap"] +colors = ["pretty", "syntect"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/dbg-pls/debug-derive/Cargo.toml b/dbg-pls/debug-derive/Cargo.toml new file mode 100644 index 0000000..5a09aa9 --- /dev/null +++ b/dbg-pls/debug-derive/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "dbg-pls-derive" +version = "0.2.1" +authors = ["Conrad Ludgate "] +edition = "2018" +description = "derive(Debug)" +license = "MIT" +repository = "https://github.com/conradludgate/dbg-pls" +readme = "../README.md" + +[lib] +proc-macro = true + +[dependencies] +quote = "1.0" +syn = "1.0" +proc-macro2 = "1.0" diff --git a/dbg-pls/debug-derive/src/debug.rs b/dbg-pls/debug-derive/src/debug.rs new file mode 100644 index 0000000..58cbb93 --- /dev/null +++ b/dbg-pls/debug-derive/src/debug.rs @@ -0,0 +1,117 @@ +use crate::pat::{named_idents, unnamed_idents, PatternImpl}; +use proc_macro2::{Ident, TokenStream as TokenStream2}; +use quote::{quote, ToTokens}; +use syn::{spanned::Spanned, Data, DataEnum, DataStruct, DeriveInput, Fields, Path, Variant}; + +pub struct DebugImpl(pub T); + +impl ToTokens for DebugImpl<(Path, DeriveInput)> { + fn to_tokens(&self, tokens: &mut TokenStream2) { + let (path, input) = &self.0; + + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + + let ident = &input.ident; + let body = DebugImpl((ident, &input.data)); + + tokens.extend(quote! { + impl #impl_generics #path::DebugPls for #ident #ty_generics #where_clause { + fn fmt(&self, f: #path::Formatter<'_>) { + #body + } + } + }) + } +} + +impl<'a> ToTokens for DebugImpl<(&'a Ident, &'a Data)> { + fn to_tokens(&self, tokens: &mut TokenStream2) { + let (name, data) = self.0; + match data { + Data::Struct(s) => DebugImpl((name, s)).to_tokens(tokens), + Data::Enum(e) => DebugImpl((name, e)).to_tokens(tokens), + Data::Union(_) => tokens + .extend(syn::Error::new(self.span(), "unions not supported").into_compile_error()), + } + } +} + +impl<'a> ToTokens for DebugImpl<(&'a Ident, &'a DataStruct)> { + fn to_tokens(&self, tokens: &mut TokenStream2) { + let (name, data) = self.0; + let pat = PatternImpl(&data.fields); + tokens.extend(quote! { + let #name #pat = self; + }); + DebugImpl((name, &data.fields)).to_tokens(tokens) + } +} + +impl<'a> ToTokens for DebugImpl<(&'a Ident, &'a DataEnum)> { + fn to_tokens(&self, tokens: &mut TokenStream2) { + let (name, data) = self.0; + let variants = data.variants.iter().map(|v| DebugImpl((name, v))); + tokens.extend(quote! { + match self { + #( #variants )* + } + }); + } +} + +impl<'a> ToTokens for DebugImpl<(&'a Ident, &'a Variant)> { + fn to_tokens(&self, tokens: &mut TokenStream2) { + let (name, variant) = self.0; + let Variant { + attrs: _, + ident, + fields, + discriminant: _, + } = &variant; + let pattern = PatternImpl(fields); + let debug = DebugImpl((ident, fields)); + + tokens.extend(quote! { + #name::#ident #pattern => { #debug } + }); + } +} + +impl<'a> ToTokens for DebugImpl<(&'a Ident, &'a Fields)> { + fn to_tokens(&self, tokens: &mut TokenStream2) { + let (name, fields) = self.0; + let name = name.to_string(); + match fields { + Fields::Named(named) => { + tokens.extend(quote! { + f.debug_struct(#name) + }); + named_idents(named).for_each(|ident| { + let name = ident.to_string(); + tokens.extend(quote! { + .field(#name, #ident) + }) + }); + tokens.extend(quote! { + .finish() + }); + } + Fields::Unnamed(unnamed) => { + tokens.extend(quote! { + f.debug_tuple_struct(#name) + }); + unnamed_idents(unnamed).for_each(|ident| { + tokens.extend(quote! { + .field(#ident) + }) + }); + tokens.extend(quote! { + .finish() + }); + } + Fields::Unit => tokens.extend(quote! { + f.debug_ident(#name) + }), + } + } +} diff --git a/dbg-pls/debug-derive/src/lib.rs b/dbg-pls/debug-derive/src/lib.rs new file mode 100644 index 0000000..43d7643 --- /dev/null +++ b/dbg-pls/debug-derive/src/lib.rs @@ -0,0 +1,61 @@ +use debug::DebugImpl; +use predicate::predicate; +use proc_macro::TokenStream; +use quote::ToTokens; +use syn::{parse_macro_input, DeriveInput, parse_quote, Attribute, Path}; + +mod debug; +mod pat; +mod predicate; + +/// Derives the standard `DebugPls` implementation. +/// +/// Works exactly like [`Debug`] +#[proc_macro_derive(DebugPls, attributes(dbg_pls))] +pub fn derive(input: TokenStream) -> TokenStream { + let mut input = parse_macro_input!(input as DeriveInput); + + let path = match get_crate(&input.attrs) { + Ok(path) => path, + Err(err) => return err.into_compile_error().into_token_stream().into(), + }; + + predicate(&mut input, path.clone()); + DebugImpl((path, input)).into_token_stream().into() +} + +fn get_crate(attrs: &[Attribute]) -> syn::Result { + fn parse_crate(lit: syn::Lit) -> syn::Result { + match lit { + syn::Lit::Str(s) => syn::parse_str(&s.value()), + _ => Err(syn::Error::new(lit.span(), "invalid crate name")), + } + } + + fn parse_meta(meta: syn::Meta) -> Option> { + if let syn::Meta::List(list) = meta { + for meta in list.nested { + if let syn::NestedMeta::Meta(syn::Meta::NameValue(nv)) = meta { + if let Some(ident) = nv.path.get_ident() { + if *ident == "crate" { + return Some(parse_crate(nv.lit)); + } + } + } + } + } + None + } + + for attr in attrs { + if let Some(ident) = attr.path.get_ident() { + if *ident == "dbg_pls" { + if let Some(path) = parse_meta(Attribute::parse_meta(attr)?).transpose()? { + return Ok(path); + } + } + } + } + + Ok(parse_quote! { ::dbg_pls }) +} diff --git a/dbg-pls/debug-derive/src/pat.rs b/dbg-pls/debug-derive/src/pat.rs new file mode 100644 index 0000000..850d15d --- /dev/null +++ b/dbg-pls/debug-derive/src/pat.rs @@ -0,0 +1,46 @@ +use proc_macro2::{Delimiter, Group, Ident, TokenStream as TokenStream2, TokenTree}; +use quote::{format_ident, quote, ToTokens}; +use syn::{spanned::Spanned, Fields, FieldsNamed, FieldsUnnamed}; + +pub struct PatternImpl(pub T); + +impl<'a> ToTokens for PatternImpl<&'a Fields> { + fn to_tokens(&self, tokens: &mut TokenStream2) { + match &self.0 { + Fields::Named(named) => PatternImpl(named).to_tokens(tokens), + Fields::Unnamed(unnamed) => PatternImpl(unnamed).to_tokens(tokens), + Fields::Unit => {} + } + } +} + +impl<'a> ToTokens for PatternImpl<&'a FieldsNamed> { + fn to_tokens(&self, tokens: &mut TokenStream2) { + let idents = named_idents(self.0); + let inner = quote! { #(#idents),* }; + tokens.extend([TokenTree::Group(Group::new(Delimiter::Brace, inner))]) + } +} + +impl<'a> ToTokens for PatternImpl<&'a FieldsUnnamed> { + fn to_tokens(&self, tokens: &mut TokenStream2) { + let idents = unnamed_idents(self.0); + let inner = quote! { #(#idents),* }; + tokens.extend([TokenTree::Group(Group::new(Delimiter::Parenthesis, inner))]) + } +} + +pub fn unnamed_idents(fields: &FieldsUnnamed) -> impl Iterator + '_ { + fields + .unnamed + .iter() + .enumerate() + .map(|(i, field)| format_ident!("val{}", i, span = field.span())) +} + +pub fn named_idents(fields: &FieldsNamed) -> impl Iterator + '_ { + fields + .named + .iter() + .map(|field| field.ident.as_ref().unwrap()) +} diff --git a/dbg-pls/debug-derive/src/predicate.rs b/dbg-pls/debug-derive/src/predicate.rs new file mode 100644 index 0000000..6df829c --- /dev/null +++ b/dbg-pls/debug-derive/src/predicate.rs @@ -0,0 +1,57 @@ +use quote::format_ident; +use syn::{ + punctuated::Punctuated, token, Data, DeriveInput, Field, Fields, Path, PredicateType, + TraitBound, TypeParamBound, WhereClause, WherePredicate, +}; + +pub fn predicate(input: &mut DeriveInput, path: Path) { + let mut pred = Pred { + wc: input.generics.make_where_clause(), + path, + }; + + match &input.data { + Data::Struct(s) => pred.fields(&s.fields), + Data::Enum(e) => e.variants.iter().for_each(|var| pred.fields(&var.fields)), + Data::Union(_) => todo!(), + } +} + +struct Pred<'a> { + wc: &'a mut WhereClause, + path: Path, +} + +impl<'a> Pred<'a> { + fn dbg_pls(&mut self, field: &Field) { + let mut bounds = Punctuated::new(); + + let mut path = self.path.clone(); + path.segments.push(syn::PathSegment { + ident: format_ident!("DebugPls"), + arguments: syn::PathArguments::None, + }); + + bounds.push(TypeParamBound::Trait(TraitBound { + paren_token: None, + modifier: syn::TraitBoundModifier::None, + lifetimes: None, + path, + })); + + self.wc.predicates.push(WherePredicate::Type(PredicateType { + lifetimes: None, + bounded_ty: field.ty.clone(), + colon_token: token::Colon::default(), + bounds, + })) + } + + fn fields(&mut self, fields: &Fields) { + match fields { + syn::Fields::Named(named) => named.named.iter().for_each(|f| self.dbg_pls(f)), + syn::Fields::Unnamed(unnamed) => unnamed.unnamed.iter().for_each(|f| self.dbg_pls(f)), + syn::Fields::Unit => {} + } + } +} diff --git a/dbg-pls/src/colors.rs b/dbg-pls/src/colors.rs new file mode 100644 index 0000000..2717720 --- /dev/null +++ b/dbg-pls/src/colors.rs @@ -0,0 +1,175 @@ +// use bat::PrettyPrinter; + +// use crate::{pretty::process, DebugPls}; + +// /// Prints the pretty printed code to stdout +// pub fn print_colorful(value: &dyn DebugPls) { +// let output = process(value); +// let _ = PrettyPrinter::new() +// .input_from_bytes(output.as_bytes()) +// .language("rust") +// .line_numbers(true) +// .print(); +// } + +use std::io::Cursor; + +use once_cell::sync::OnceCell; +use syntect::{ + easy::HighlightLines, + highlighting::{Theme, ThemeSet}, + parsing::{SyntaxDefinition, SyntaxSet, SyntaxSetBuilder}, + util::{as_24_bit_terminal_escaped, LinesWithEndings}, +}; + +use crate::{pretty::pretty_string, DebugPls, Formatter}; + +fn syntax() -> &'static SyntaxSet { + static INSTANCE: OnceCell = OnceCell::new(); + INSTANCE.get_or_init(|| { + let mut syntax_set = SyntaxSetBuilder::new(); + syntax_set.add( + SyntaxDefinition::load_from_str( + include_str!("../assets/syntaxes/Rust/Rust.sublime-syntax"), + true, + None, + ) + .unwrap(), + ); + syntax_set.build() + }) +} + +fn theme() -> &'static Theme { + static INSTANCE: OnceCell = OnceCell::new(); + INSTANCE.get_or_init(|| { + let s = include_str!("../assets/themes/sublime-monokai-extended/Monokai Extended.tmTheme"); + ThemeSet::load_from_reader(&mut Cursor::new(s.as_bytes())).unwrap() + }) +} + +fn highlight(s: &str, mut w: impl std::fmt::Write) -> std::fmt::Result { + let ps = syntax(); + let syntax = ps.find_syntax_by_name("Rust").unwrap(); + let theme = theme(); + + let mut h = HighlightLines::new(syntax, theme); + + for line in LinesWithEndings::from(s) { + let ranges = h.highlight(line, ps); + write!(w, "{}", as_24_bit_terminal_escaped(&ranges[..], false))?; + } + write!(w, "\x1b[0m") // reset the color +} + +/// Implementation detail for the `color!` macro +pub struct ColorStr<'a>(pub &'a str); + +impl<'a> std::fmt::Display for ColorStr<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let expr = syn::parse_str(self.0).map_err(|_| std::fmt::Error)?; + highlight(&pretty_string(expr), f) + } +} + +struct Color<'a>(&'a dyn DebugPls); + +impl<'a> std::fmt::Debug for Color<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + highlight(&pretty_string(Formatter::process(self.0)), f) + } +} + +impl<'a> std::fmt::Display for Color<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} + +#[cfg_attr(docsrs, doc(cfg(feature = "colors")))] +/// Wraps a [`Debug`] type into a [`std::fmt::Debug`] type for use in regular [`format!`] +pub fn color(value: &impl DebugPls) -> impl std::fmt::Debug + std::fmt::Display + '_ { + Color(value) +} + +#[cfg_attr(docsrs, doc(cfg(feature = "colors")))] +#[macro_export] +/// Prints and returns the value of a given expression for quick and dirty +/// debugging. Same as [`std::dbg`] +/// +/// An example: +/// +/// ```rust +/// # use dbg_pls::color; +/// let a = 2; +/// let b = color!(a * 2) + 1; +/// // ^-- prints: [src/main.rs:2] a * 2 = 4 +/// // with syntax highlighting +/// assert_eq!(b, 5); +/// ``` +/// +/// The macro works by using the [`DebugPls`] implementation of the type of +/// the given expression to print the value to [stderr] along with the +/// source location of the macro invocation as well as the source code +/// of the expression. +/// +/// Invoking the macro on an expression moves and takes ownership of it +/// before returning the evaluated expression unchanged. If the type +/// of the expression does not implement `Copy` and you don't want +/// to give up ownership, you can instead borrow with `color!(&expr)` +/// for some expression `expr`. +/// +/// The `color!` macro works exactly the same in release builds. +/// This is useful when debugging issues that only occur in release +/// builds or when debugging in release mode is significantly faster. +/// +/// Note that the macro is intended as a debugging tool and therefore you +/// should avoid having uses of it in version control for long periods +/// (other than in tests and similar). +/// Debug output from production code is better done with other facilities +/// such as the [`debug!`] macro from the [`log`] crate. +/// +/// [stderr]: https://en.wikipedia.org/wiki/Standard_streams#Standard_error_(stderr) +/// [`debug!`]: https://docs.rs/log/*/log/macro.debug.html +/// [`log`]: https://crates.io/crates/log +macro_rules! color { + () => { + ::std::eprintln!("[{}:{}]", ::std::file!(), ::std::line!()) + }; + ($val:expr $(,)?) => { + match $val { + tmp => { + ::std::eprintln!( + "[{}:{}] {} => {}", + ::std::file!(), + ::std::line!(), + $crate::__private::ColorStr(::std::stringify!($val)), + $crate::color(&tmp) + ); + tmp + } + } + }; + ($($val:expr),+ $(,)?) => { + ($($crate::color!($val)),+,) + }; +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use crate::color; + + #[test] + fn colors() { + let map = color! { + HashMap::from([ + ("hello", 1), + ("world", 2), + ]) + }; + // map is moved through properly + assert_eq!(map, HashMap::from([("hello", 1), ("world", 2),])); + } +} diff --git a/dbg-pls/src/debug_list.rs b/dbg-pls/src/debug_list.rs new file mode 100644 index 0000000..6719ac1 --- /dev/null +++ b/dbg-pls/src/debug_list.rs @@ -0,0 +1,69 @@ +use crate::{DebugPls, Formatter}; + +/// A helper designed to assist with creation of +/// [`DebugPls`] implementations for list-like structures. +/// +/// # Examples +/// +/// ```rust +/// use dbg_pls::{pretty, DebugPls, Formatter}; +/// +/// struct Foo(Vec); +/// +/// impl DebugPls for Foo { +/// fn fmt(&self, f: Formatter<'_>) { +/// f.debug_list().entries(&self.0).finish() +/// } +/// } +/// +/// let value = Foo(vec![10, 11]); +/// assert_eq!(format!("{}", pretty(&value)), "[10, 11]"); +/// ``` +pub struct DebugList<'a> { + formatter: Formatter<'a>, + expr: syn::ExprArray, +} + +impl<'a> DebugList<'a> { + pub(crate) fn new(formatter: Formatter<'a>) -> Self { + DebugList { + formatter, + expr: syn::ExprArray { + attrs: vec![], + bracket_token: syn::token::Bracket::default(), + elems: syn::punctuated::Punctuated::default(), + }, + } + } + + /// Adds a new entry to the list output. + #[must_use] + pub fn entry(mut self, entry: &dyn DebugPls) -> Self { + self.expr.elems.push(Formatter::process(entry)); + self + } + + /// Adds all the entries to the list output. + #[must_use] + pub fn entries(mut self, entries: I) -> Self + where + D: DebugPls, + I: IntoIterator, + { + self.extend(entries); + self + } + + /// Closes off the list + pub fn finish(self) { + self.formatter.write_expr(self.expr); + } +} + +impl<'f, D: DebugPls> Extend for DebugList<'f> { + fn extend>(&mut self, iter: T) { + self.expr + .elems + .extend(iter.into_iter().map(|entry| Formatter::process(&entry))); + } +} diff --git a/dbg-pls/src/debug_map.rs b/dbg-pls/src/debug_map.rs new file mode 100644 index 0000000..128b2e3 --- /dev/null +++ b/dbg-pls/src/debug_map.rs @@ -0,0 +1,126 @@ +use std::iter::FromIterator; + +use syn::punctuated::Punctuated; + +use crate::{DebugPls, Formatter}; + +/// A helper designed to assist with creation of +/// [`DebugPls`] implementations for maps. +/// +/// # Examples +/// +/// ```rust +/// use dbg_pls::{pretty, DebugPls, Formatter}; +/// use std::collections::BTreeMap; +/// +/// struct Foo(BTreeMap); +/// +/// impl DebugPls for Foo { +/// fn fmt(&self, f: Formatter) { +/// f.debug_map().entries(&self.0).finish() +/// } +/// } +/// let mut value = Foo(BTreeMap::from([ +/// ("Hello".to_string(), 5), +/// ("World".to_string(), 10), +/// ])); +/// assert_eq!( +/// format!("{}", pretty(&value)), +/// "{ +/// [\"Hello\"] = 5; +/// [\"World\"] = 10; +/// }", +/// ); +/// ``` +pub struct DebugMap<'a> { + formatter: Formatter<'a>, + set: syn::Block, + key: Option, +} + +impl<'a> DebugMap<'a> { + pub(crate) fn new(formatter: Formatter<'a>) -> Self { + DebugMap { + formatter, + set: syn::Block { + brace_token: syn::token::Brace::default(), + stmts: vec![], + }, + key: None, + } + } + + /// Adds the key part to the map output. + /// + /// # Panics + /// + /// `key` must be called before `value` and each call to `key` must be followed + /// by a corresponding call to `value`. Otherwise this method will panic. + #[must_use] + pub fn key(mut self, key: &dyn DebugPls) -> Self { + if self.key.replace(Formatter::process(key)).is_some() { + panic!("attempted to begin a new map entry without completing the previous one"); + } + self + } + + /// Adds the value part to the map output. + /// + /// # Panics + /// + /// `key` must be called before `value` and each call to `key` must be followed + /// by a corresponding call to `value`. Otherwise this method will panic. + #[must_use] + pub fn value(mut self, value: &dyn DebugPls) -> Self { + let key = self + .key + .take() + .expect("attempted to format a map value before its key"); + let value = Formatter::process(value); + let entry = syn::ExprAssign { + attrs: vec![], + left: Box::new( + syn::ExprArray { + attrs: vec![], + bracket_token: syn::token::Bracket::default(), + elems: Punctuated::from_iter([key]), + } + .into(), + ), + eq_token: syn::token::Eq::default(), + right: Box::new(value), + }; + self.set + .stmts + .push(syn::Stmt::Semi(entry.into(), syn::token::Semi::default())); + self + } + + /// Adds the entry to the map output. + #[must_use] + pub fn entry(self, key: &dyn DebugPls, value: &dyn DebugPls) -> Self { + self.key(key).value(value) + } + + /// Adds all the entries to the map output. + #[must_use] + pub fn entries(self, entries: I) -> Self + where + K: DebugPls, + V: DebugPls, + I: IntoIterator, + { + entries + .into_iter() + .fold(self, |f, (key, value)| f.entry(&key, &value)) + } + + /// Closes off the map. + pub fn finish(self) { + self.formatter.write_expr(syn::ExprBlock { + attrs: vec![], + label: None, + block: self.set, + }); + } +} diff --git a/dbg-pls/src/debug_set.rs b/dbg-pls/src/debug_set.rs new file mode 100644 index 0000000..7e1b785 --- /dev/null +++ b/dbg-pls/src/debug_set.rs @@ -0,0 +1,80 @@ +use crate::{DebugPls, Formatter}; + +/// A helper designed to assist with creation of +/// [`DebugPls`] implementations for sets. +/// +/// # Examples +/// +/// ```rust +/// use dbg_pls::{pretty, DebugPls, Formatter}; +/// use std::collections::BTreeSet; +/// +/// struct Foo(BTreeSet); +/// +/// impl DebugPls for Foo { +/// fn fmt(&self, f: Formatter) { +/// f.debug_set().entries(&self.0).finish() +/// } +/// } +/// let mut value = Foo(BTreeSet::from([ +/// "Hello".to_string(), +/// "World".to_string(), +/// ])); +/// assert_eq!( +/// format!("{}", pretty(&value)), +/// "{ +/// \"Hello\"; +/// \"World\" +/// }", +/// ); +/// ``` +pub struct DebugSet<'a> { + formatter: Formatter<'a>, + set: syn::Block, +} + +impl<'a> DebugSet<'a> { + pub(crate) fn new(formatter: Formatter<'a>) -> Self { + DebugSet { + formatter, + set: syn::Block { + brace_token: syn::token::Brace::default(), + stmts: vec![], + }, + } + } + + /// Adds the entry to the set output. + #[must_use] + pub fn entry(mut self, value: &dyn DebugPls) -> Self { + let expr = Formatter::process(value); + self.set + .stmts + .push(syn::Stmt::Semi(expr, syn::token::Semi::default())); + self + } + + /// Adds all the entries to the set output. + #[must_use] + pub fn entries(self, entries: I) -> Self + where + V: DebugPls, + I: IntoIterator, + { + entries.into_iter().fold(self, |f, entry| f.entry(&entry)) + } + + /// Closes off the set. + pub fn finish(mut self) { + // remove the last semicolon + if let Some(syn::Stmt::Semi(entry, _)) = self.set.stmts.pop() { + self.set.stmts.push(syn::Stmt::Expr(entry)); + } + + self.formatter.write_expr(syn::ExprBlock { + attrs: vec![], + label: None, + block: self.set, + }); + } +} diff --git a/dbg-pls/src/debug_struct.rs b/dbg-pls/src/debug_struct.rs new file mode 100644 index 0000000..2153fde --- /dev/null +++ b/dbg-pls/src/debug_struct.rs @@ -0,0 +1,77 @@ +use syn::__private::Span; + +use crate::{DebugPls, Formatter}; + +/// A helper designed to assist with creation of +/// [`DebugPls`] implementations for structs. +/// +/// # Examples +/// +/// ```rust +/// use dbg_pls::{pretty, DebugPls, Formatter}; +/// +/// struct Foo { +/// bar: i32, +/// baz: String, +/// } +/// +/// impl DebugPls for Foo { +/// fn fmt(&self, f: Formatter) { +/// f.debug_struct("Foo") +/// .field("bar", &self.bar) +/// .field("baz", &self.baz) +/// .finish() +/// } +/// } +/// let value = Foo { +/// bar: 10, +/// baz: "Hello World".to_string(), +/// }; +/// assert_eq!( +/// format!("{}", pretty(&value)), +/// "Foo { bar: 10, baz: \"Hello World\" }", +/// ); +/// ``` +pub struct DebugStruct<'a> { + formatter: Formatter<'a>, + expr: syn::ExprStruct, +} + +impl<'a> DebugStruct<'a> { + pub(crate) fn new(formatter: Formatter<'a>, name: &str) -> Self { + DebugStruct { + formatter, + expr: syn::ExprStruct { + attrs: vec![], + path: syn::Ident::new(name, Span::call_site()).into(), + brace_token: syn::token::Brace::default(), + fields: syn::punctuated::Punctuated::new(), + dot2_token: None, + rest: None, + }, + } + } + + /// Adds the field to the struct output. + #[must_use] + pub fn field(mut self, name: &str, value: &dyn DebugPls) -> Self { + self.expr.fields.push(syn::FieldValue { + expr: Formatter::process(value), + attrs: vec![], + member: syn::Member::Named(syn::Ident::new(name, Span::call_site())), + colon_token: Some(syn::token::Colon::default()), + }); + self + } + + /// Closes off the struct. + pub fn finish(self) { + self.formatter.write_expr(self.expr); + } + + /// Closes off the struct with `..`. + pub fn finish_non_exhaustive(mut self) { + self.expr.dot2_token = Some(syn::token::Dot2::default()); + self.finish(); + } +} diff --git a/dbg-pls/src/debug_tuple.rs b/dbg-pls/src/debug_tuple.rs new file mode 100644 index 0000000..875f1bf --- /dev/null +++ b/dbg-pls/src/debug_tuple.rs @@ -0,0 +1,53 @@ +use crate::{DebugPls, Formatter}; + +/// A helper designed to assist with creation of +/// [`DebugPls`] implementations for tuples. +/// +/// # Examples +/// +/// ```rust +/// use dbg_pls::{pretty, DebugPls, Formatter}; +/// +/// struct Foo(i32, String); +/// +/// impl DebugPls for Foo { +/// fn fmt(&self, f: Formatter) { +/// f.debug_tuple() +/// .field(&self.0) +/// .field(&self.1) +/// .finish() +/// } +/// } +/// +/// let value = Foo(10, "Hello".to_string()); +/// assert_eq!(format!("{}", pretty(&value)), "(10, \"Hello\")"); +/// ``` +pub struct DebugTuple<'a> { + formatter: Formatter<'a>, + expr: syn::ExprTuple, +} + +impl<'a> DebugTuple<'a> { + pub(crate) fn new(formatter: Formatter<'a>) -> Self { + DebugTuple { + formatter, + expr: syn::ExprTuple { + attrs: vec![], + paren_token: syn::token::Paren::default(), + elems: syn::punctuated::Punctuated::new(), + }, + } + } + + /// Adds the field to the tuple output. + #[must_use] + pub fn field(mut self, value: &dyn DebugPls) -> Self { + self.expr.elems.push(Formatter::process(value)); + self + } + + /// Closes off the tuple. + pub fn finish(self) { + self.formatter.write_expr(self.expr); + } +} diff --git a/dbg-pls/src/debug_tuple_struct.rs b/dbg-pls/src/debug_tuple_struct.rs new file mode 100644 index 0000000..c882458 --- /dev/null +++ b/dbg-pls/src/debug_tuple_struct.rs @@ -0,0 +1,60 @@ +use syn::__private::Span; + +use crate::{DebugPls, Formatter}; + +/// A helper designed to assist with creation of +/// [`DebugPls`] implementations for tuple structs. +/// +/// # Examples +/// +/// ```rust +/// use dbg_pls::{pretty, DebugPls, Formatter}; +/// +/// struct Foo(i32, String); +/// +/// impl DebugPls for Foo { +/// fn fmt(&self, f: Formatter) { +/// f.debug_tuple_struct("Foo") +/// .field(&self.0) +/// .field(&self.1) +/// .finish() +/// } +/// } +/// +/// let value = Foo(10, "Hello".to_string()); +/// assert_eq!(format!("{}", pretty(&value)), "Foo(10, \"Hello\")"); +/// ``` +pub struct DebugTupleStruct<'a> { + formatter: Formatter<'a>, + expr: syn::ExprCall, +} + +impl<'a> DebugTupleStruct<'a> { + pub(crate) fn new(formatter: Formatter<'a>, name: &str) -> Self { + DebugTupleStruct { + formatter, + expr: syn::ExprCall { + attrs: vec![], + func: Box::new(syn::Expr::Path(syn::ExprPath { + attrs: vec![], + qself: None, + path: syn::Ident::new(name, Span::call_site()).into(), + })), + paren_token: syn::token::Paren::default(), + args: syn::punctuated::Punctuated::new(), + }, + } + } + + /// Adds the field to the tuple struct output. + #[must_use] + pub fn field(mut self, value: &dyn DebugPls) -> Self { + self.expr.args.push(Formatter::process(value)); + self + } + + /// Closes off the tuple struct. + pub fn finish(self) { + self.formatter.write_expr(self.expr); + } +} diff --git a/dbg-pls/src/impls.rs b/dbg-pls/src/impls.rs new file mode 100644 index 0000000..fb2b84d --- /dev/null +++ b/dbg-pls/src/impls.rs @@ -0,0 +1,2 @@ +mod syn_impls; +mod std; diff --git a/dbg-pls/src/impls/std.rs b/dbg-pls/src/impls/std.rs new file mode 100644 index 0000000..0f6ebc0 --- /dev/null +++ b/dbg-pls/src/impls/std.rs @@ -0,0 +1,162 @@ +mod collections; + +use std::{ + ops::ControlFlow, + rc::Rc, + sync::{Arc, Mutex, MutexGuard, TryLockError}, + task::Poll, +}; + +use crate::{DebugPls, Formatter}; +use syn::__private::Span; + +impl DebugPls for Box { + fn fmt(&self, f: Formatter<'_>) { + DebugPls::fmt(&**self, f); + } +} + +impl<'a, D: DebugPls + ?Sized> DebugPls for &'a D { + fn fmt(&self, f: Formatter<'_>) { + D::fmt(self, f); + } +} + +impl DebugPls for Rc { + fn fmt(&self, f: Formatter<'_>) { + DebugPls::fmt(&**self, f); + } +} + +impl DebugPls for Arc { + fn fmt(&self, f: Formatter<'_>) { + DebugPls::fmt(&**self, f); + } +} + +impl DebugPls for MutexGuard<'_, T> { + fn fmt(&self, f: Formatter<'_>) { + DebugPls::fmt(&**self, f); + } +} + +impl DebugPls for Mutex { + fn fmt(&self, f: Formatter<'_>) { + let d = f.debug_struct("Mutex"); + match self.try_lock() { + Ok(guard) => d.field("data", &&*guard), + Err(TryLockError::Poisoned(err)) => d.field("data", &&**err.get_ref()), + Err(TryLockError::WouldBlock) => d.field("data", &""), + } + .field("poisoned", &self.is_poisoned()) + .finish_non_exhaustive(); + } +} + +macro_rules! debug_integers { + ($($T:ident)*) => {$( + impl DebugPls for $T { + fn fmt(&self, f: Formatter<'_>) { + let mut buf = itoa::Buffer::new(); + f.write_expr(syn::ExprLit { + attrs: vec![], + lit: syn::LitInt::new(buf.format(*self), Span::call_site()).into(), + }); + } + } + )*}; +} + +debug_integers! { + i8 i16 i32 i64 i128 isize + u8 u16 u32 u64 u128 usize +} + +macro_rules! debug_floats { + ($ty:ident) => { + impl DebugPls for $ty { + fn fmt(&self, f: Formatter<'_>) { + let mut buf = ryu::Buffer::new(); + f.write_expr(syn::ExprLit { + attrs: vec![], + lit: syn::LitFloat::new(buf.format(*self), Span::call_site()).into(), + }); + } + } + }; +} + +debug_floats! { f32 } +debug_floats! { f64 } + +impl DebugPls for bool { + fn fmt(&self, f: Formatter<'_>) { + match self { + true => f.debug_ident("true"), + false => f.debug_ident("false"), + } + } +} + +impl DebugPls for [D] { + fn fmt(&self, f: Formatter<'_>) { + f.debug_list().entries(self).finish(); + } +} + +impl DebugPls for [D; N] { + fn fmt(&self, f: Formatter<'_>) { + f.debug_list().entries(self).finish(); + } +} + +impl DebugPls for str { + fn fmt(&self, f: Formatter<'_>) { + f.write_expr(syn::ExprLit { + attrs: vec![], + lit: syn::LitStr::new(self, Span::call_site()).into(), + }); + } +} + +impl DebugPls for String { + fn fmt(&self, f: Formatter<'_>) { + DebugPls::fmt(self.as_str(), f); + } +} + +impl DebugPls for Result { + fn fmt(&self, f: Formatter<'_>) { + match self { + Ok(t) => f.debug_tuple_struct("Ok").field(t).finish(), + Err(e) => f.debug_tuple_struct("Err").field(e).finish(), + } + } +} + +impl DebugPls for ControlFlow { + fn fmt(&self, f: Formatter<'_>) { + match self { + ControlFlow::Break(b) => f.debug_tuple_struct("Break").field(b).finish(), + ControlFlow::Continue(c) => f.debug_tuple_struct("Continue").field(c).finish(), + } + } +} + +impl DebugPls for Option { + fn fmt(&self, f: Formatter<'_>) { + match self { + Some(t) => f.debug_tuple_struct("Some").field(t).finish(), + None => f.debug_ident("None"), + } + } +} + +impl DebugPls for Poll { + fn fmt(&self, f: Formatter<'_>) { + match self { + Poll::Ready(t) => f.debug_tuple_struct("Ready").field(t).finish(), + Poll::Pending => f.debug_ident("Pending"), + } + } +} diff --git a/dbg-pls/src/impls/std/collections.rs b/dbg-pls/src/impls/std/collections.rs new file mode 100644 index 0000000..4ee6c7f --- /dev/null +++ b/dbg-pls/src/impls/std/collections.rs @@ -0,0 +1,51 @@ +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque, LinkedList, BinaryHeap}; + +use crate::{DebugPls, Formatter}; + +impl DebugPls for HashMap { + fn fmt(&self, f: Formatter<'_>) { + f.debug_map().entries(self).finish(); + } +} + +impl DebugPls for BTreeMap { + fn fmt(&self, f: Formatter<'_>) { + f.debug_map().entries(self).finish(); + } +} + +impl DebugPls for HashSet { + fn fmt(&self, f: Formatter<'_>) { + f.debug_set().entries(self).finish(); + } +} + +impl DebugPls for BTreeSet { + fn fmt(&self, f: Formatter<'_>) { + f.debug_set().entries(self).finish(); + } +} + +impl DebugPls for Vec { + fn fmt(&self, f: Formatter<'_>) { + f.debug_list().entries(self).finish(); + } +} + +impl DebugPls for VecDeque { + fn fmt(&self, f: Formatter<'_>) { + f.debug_list().entries(self).finish(); + } +} + +impl DebugPls for LinkedList { + fn fmt(&self, f: Formatter<'_>) { + f.debug_list().entries(self).finish(); + } +} + +impl DebugPls for BinaryHeap { + fn fmt(&self, f: Formatter<'_>) { + f.debug_list().entries(self).finish(); + } +} diff --git a/dbg-pls/src/impls/syn_impls.rs b/dbg-pls/src/impls/syn_impls.rs new file mode 100644 index 0000000..1b5654c --- /dev/null +++ b/dbg-pls/src/impls/syn_impls.rs @@ -0,0 +1,155 @@ +use syn::{ + punctuated::{Pair, Punctuated}, + token::{Bracket, Comma}, + Attribute, Expr, ExprArray, ExprLit, Lit, LitInt, LitStr, +}; + +use crate::{DebugPls, Formatter}; + +impl DebugPls for Expr { + fn fmt(&self, f: Formatter<'_>) { + match self { + Expr::Array(val0) => f.debug_tuple_struct("Array").field(val0).finish(), + // Expr::Assign(val0) => f.debug_tuple_struct("Assign").field(val0).finish(), + // Expr::AssignOp(val0) => f.debug_tuple_struct("AssignOp").field(val0).finish(), + // Expr::Async(val0) => f.debug_tuple_struct("Async").field(val0).finish(), + // Expr::Await(val0) => f.debug_tuple_struct("Await").field(val0).finish(), + // Expr::Binary(val0) => f.debug_tuple_struct("Binary").field(val0).finish(), + // Expr::Block(val0) => f.debug_tuple_struct("Block").field(val0).finish(), + // Expr::Box(val0) => f.debug_tuple_struct("Box").field(val0).finish(), + // Expr::Break(val0) => f.debug_tuple_struct("Break").field(val0).finish(), + // Expr::Call(val0) => f.debug_tuple_struct("Call").field(val0).finish(), + // Expr::Cast(val0) => f.debug_tuple_struct("Cast").field(val0).finish(), + // Expr::Closure(val0) => f.debug_tuple_struct("Closure").field(val0).finish(), + // Expr::Continue(val0) => f.debug_tuple_struct("Continue").field(val0).finish(), + // Expr::Field(val0) => f.debug_tuple_struct("Field").field(val0).finish(), + // Expr::ForLoop(val0) => f.debug_tuple_struct("ForLoop").field(val0).finish(), + // Expr::Group(val0) => f.debug_tuple_struct("Group").field(val0).finish(), + // Expr::If(val0) => f.debug_tuple_struct("If").field(val0).finish(), + // Expr::Index(val0) => f.debug_tuple_struct("Index").field(val0).finish(), + // Expr::Let(val0) => f.debug_tuple_struct("Let").field(val0).finish(), + Expr::Lit(val0) => f.debug_tuple_struct("Lit").field(val0).finish(), + // Expr::Loop(val0) => f.debug_tuple_struct("Loop").field(val0).finish(), + // Expr::Macro(val0) => f.debug_tuple_struct("Macro").field(val0).finish(), + // Expr::Match(val0) => f.debug_tuple_struct("Match").field(val0).finish(), + // Expr::MethodCall(val0) => f.debug_tuple_struct("MethodCall").field(val0).finish(), + // Expr::Paren(val0) => f.debug_tuple_struct("Paren").field(val0).finish(), + // Expr::Path(val0) => f.debug_tuple_struct("Path").field(val0).finish(), + // Expr::Range(val0) => f.debug_tuple_struct("Range").field(val0).finish(), + // Expr::Reference(val0) => f.debug_tuple_struct("Reference").field(val0).finish(), + // Expr::Repeat(val0) => f.debug_tuple_struct("Repeat").field(val0).finish(), + // Expr::Return(val0) => f.debug_tuple_struct("Return").field(val0).finish(), + // Expr::Struct(val0) => f.debug_tuple_struct("Struct").field(val0).finish(), + // Expr::Try(val0) => f.debug_tuple_struct("Try").field(val0).finish(), + // Expr::TryBlock(val0) => f.debug_tuple_struct("TryBlock").field(val0).finish(), + // Expr::Tuple(val0) => f.debug_tuple_struct("Tuple").field(val0).finish(), + // Expr::Type(val0) => f.debug_tuple_struct("Type").field(val0).finish(), + // Expr::Unary(val0) => f.debug_tuple_struct("Unary").field(val0).finish(), + // Expr::Unsafe(val0) => f.debug_tuple_struct("Unsafe").field(val0).finish(), + // Expr::Verbatim(val0) => f.debug_tuple_struct("Verbatim").field(val0).finish(), + // Expr::While(val0) => f.debug_tuple_struct("While").field(val0).finish(), + // Expr::Yield(val0) => f.debug_tuple_struct("Yield").field(val0).finish(), + _ => todo!(), + } + } +} + +impl DebugPls for ExprArray { + fn fmt(&self, f: Formatter<'_>) { + f.debug_struct("ExprArray") + .field("attrs", &self.attrs) + .field("bracket_token", &self.bracket_token) + .field("elems", &self.elems) + .finish(); + } +} + +impl DebugPls for ExprLit { + fn fmt(&self, f: Formatter<'_>) { + f.debug_struct("ExprLit") + .field("attrs", &self.attrs) + .field("lit", &self.lit) + .finish(); + } +} + +impl DebugPls for Lit { + fn fmt(&self, f: Formatter<'_>) { + match self { + Lit::Str(v0) => f.debug_tuple_struct("Str").field(v0).finish(), + // Lit::ByteStr(v0) => f.debug_tuple_struct("ByteStr").field(v0).finish(), + // Lit::Byte(v0) => f.debug_tuple_struct("Byte").field(v0).finish(), + // Lit::Char(v0) => f.debug_tuple_struct("Char").field(v0).finish(), + Lit::Int(v0) => f.debug_tuple_struct("Int").field(v0).finish(), + // Lit::Float(v0) => f.debug_tuple_struct("Float").field(v0).finish(), + // Lit::Bool(v0) => f.debug_tuple_struct("Bool").field(v0).finish(), + // Lit::Verbatim(v0) => f.debug_tuple_struct("Verbatim").field(v0).finish(), + _ => todo!(), + } + } +} + +impl DebugPls for LitStr { + fn fmt(&self, f: Formatter<'_>) { + f.debug_struct("LitStr") + .field("value", &self.value()) + .finish(); + } +} + +impl DebugPls for LitInt { + fn fmt(&self, f: Formatter<'_>) { + f.debug_struct("LitInt") + .field("value", &self.base10_digits()) + .finish(); + } +} + +impl DebugPls for Attribute { + fn fmt(&self, f: Formatter<'_>) { + f.debug_struct("Attribute").finish_non_exhaustive(); + } +} + +impl DebugPls for Punctuated { + fn fmt(&self, f: Formatter<'_>) { + self.pairs() + .fold(f.debug_list(), |f, pair| match pair { + Pair::Punctuated(t, p) => f.entry(t).entry(p), + Pair::End(t) => f.entry(t), + }) + .finish(); + } +} + +macro_rules! debug_units { + ($($T:ident),*) => {$( + impl DebugPls for $T { + fn fmt(&self, f: Formatter<'_>) { + f.debug_ident(stringify!($T)) + } + } + )*}; +} + +debug_units![Comma, Bracket]; + +#[cfg(test)] +mod tests { + use crate::color; + + #[test] + fn pretty_colors() { + let code = r#" + [ + "Hello, World! I am a long string", + 420, + "Wait, you can't mix and match types in arrays, is this python?", + 69, + "Nice." + ] + "#; + let expr: syn::Expr = syn::parse_str(code).unwrap(); + println!("{}", color(&expr)); + } +} diff --git a/dbg-pls/src/lib.rs b/dbg-pls/src/lib.rs new file mode 100644 index 0000000..ba8188e --- /dev/null +++ b/dbg-pls/src/lib.rs @@ -0,0 +1,707 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![warn(clippy::pedantic)] +#![forbid(unsafe_code)] +//! Syntax aware debug printing. +//! +//! Makes use of `syn` and `prettyplease` in order to provide the most +//! canonincal rust debug lines as possible, quickly. +//! +//! # Example usage +//! +//! ``` +//! use dbg_pls::{pretty, DebugPls}; +//! +//! #[derive(DebugPls, Copy, Clone)] +//! pub struct Demo { +//! foo: i32, +//! bar: &'static str, +//! } +//! +//! let mut val = [Demo { foo: 5, bar: "hello" }; 10]; +//! val[6].bar = "Hello, world! I am a very long string"; +//! +//! let output = format!("{}", pretty(&val)); +//! let expected = r#"[ +//! Demo { foo: 5, bar: "hello" }, +//! Demo { foo: 5, bar: "hello" }, +//! Demo { foo: 5, bar: "hello" }, +//! Demo { foo: 5, bar: "hello" }, +//! Demo { foo: 5, bar: "hello" }, +//! Demo { foo: 5, bar: "hello" }, +//! Demo { +//! foo: 5, +//! bar: "Hello, world! I am a very long string", +//! }, +//! Demo { foo: 5, bar: "hello" }, +//! Demo { foo: 5, bar: "hello" }, +//! Demo { foo: 5, bar: "hello" }, +//! ]"#; +//! +//! assert_eq!(output, expected); +//! ``` +//! +//! # Example with highlighting +//! +//! ``` +//! use dbg_pls::{color, DebugPls}; +//! +//! #[derive(DebugPls, Copy, Clone)] +//! pub struct Demo { +//! foo: i32, +//! bar: &'static str, +//! } +//! +//! let mut val = [Demo { foo: 5, bar: "hello" }; 10]; +//! val[6].bar = "Hello, world! I am a very long string"; +//! +//! println!("{}", color(&val)); +//! ``` +//! Outputs: +//! +//! ![](https://raw.githubusercontent.com/conradludgate/dbg-pls/5dee03187a3f83693739e0288d56da5980e1d486/readme/highlighted.png) +//! +//! # Why +//! +//! For the sake of demonstration, let's take a look at the snippet from above. +//! It provides an array of 10 `Demo` structs. You could imagine this to +//! be representative of a complex deep struct. +//! +//! ``` +//! #[derive(Debug, Copy, Clone)] +//! pub struct Demo { +//! foo: i32, +//! bar: &'static str, +//! } +//! +//! let mut val = [Demo { foo: 5, bar: "hello" }; 10]; +//! val[6].bar = "Hello, world! I am a very long string"; +//! +//! println!("{:?}", val); +//! ``` +//! +//! This outputs +//! +//! ```text +//! [Demo { foo: 5, bar: "hello" }, Demo { foo: 5, bar: "hello" }, Demo { foo: 5, bar: "hello" }, Demo { foo: 5, bar: "hello" }, Demo { foo: 5, bar: "hello" }, Demo { foo: 5, bar: "hello" }, Demo { foo: 5, bar: "Hello, world! I am a very long string" }, Demo { foo: 5, bar: "hello" }, Demo { foo: 5, bar: "hello" }, Demo { foo: 5, bar: "hello" }] +//! ``` +//! +//! Switching to the alternative output format `{:#?}` you get the following +//! +//! ```text +//! [ +//! Demo { +//! foo: 5, +//! bar: "hello", +//! }, +//! Demo { +//! foo: 5, +//! bar: "hello", +//! }, +//! Demo { +//! foo: 5, +//! bar: "hello", +//! }, +//! Demo { +//! foo: 5, +//! bar: "hello", +//! }, +//! Demo { +//! foo: 5, +//! bar: "hello", +//! }, +//! Demo { +//! foo: 5, +//! bar: "hello", +//! }, +//! Demo { +//! foo: 5, +//! bar: "Hello, world! I am a very long string", +//! }, +//! Demo { +//! foo: 5, +//! bar: "hello", +//! }, +//! Demo { +//! foo: 5, +//! bar: "hello", +//! }, +//! Demo { +//! foo: 5, +//! bar: "hello", +//! }, +//! ] +//! ``` +//! +//! Both of these are very unweildy to read through. Compare that to our `pretty` formatting: +//! +//! ``` +//! # use dbg_pls::pretty; +//! # let val = 0; +//! println!("{}", pretty(&val)); +//! ``` +//! +//! And you will see +//! +//! ```text +//! [ +//! Demo { foo: 5, bar: "hello" }, +//! Demo { foo: 5, bar: "hello" }, +//! Demo { foo: 5, bar: "hello" }, +//! Demo { foo: 5, bar: "hello" }, +//! Demo { foo: 5, bar: "hello" }, +//! Demo { foo: 5, bar: "hello" }, +//! Demo { +//! foo: 5, +//! bar: "Hello, world! I am a very long string", +//! }, +//! Demo { foo: 5, bar: "hello" }, +//! Demo { foo: 5, bar: "hello" }, +//! Demo { foo: 5, bar: "hello" }, +//! ] +//! ``` +//! +//! # How it works +//! +//! All [`DebugPls`] implementations are forced to output only valid +//! [`syn::Expr`] values. These are then formatted using [`prettyplease::unparse`]. +//! Finally, it uses [`syntect`] to provide syntax highlighting, with theme provided by +//! + +use syn::__private::{Span, TokenStream2}; + +mod impls; + +mod debug_list; +mod debug_map; +mod debug_set; +mod debug_struct; +mod debug_tuple; +mod debug_tuple_struct; +pub use debug_list::DebugList; +pub use debug_map::DebugMap; +pub use debug_set::DebugSet; +pub use debug_struct::DebugStruct; +pub use debug_tuple::DebugTuple; +pub use debug_tuple_struct::DebugTupleStruct; + +#[cfg(feature = "pretty")] +mod pretty; +#[cfg(feature = "pretty")] +pub use pretty::pretty; + +#[cfg(feature = "colors")] +mod colors; +#[cfg(feature = "colors")] +pub use colors::color; + +#[cfg(feature = "derive")] +#[cfg_attr(docsrs, doc(cfg(feature = "derive")))] +pub use dbg_pls_derive::DebugPls; + +#[doc(hidden)] +pub mod __private { + #[cfg(feature = "pretty")] + pub use crate::pretty::Str as PrettyStr; + #[cfg(feature = "colors")] + pub use crate::colors::ColorStr; +} + +/// Syntax aware pretty-printed debug formatting. +/// +/// `DebugPls` should format the output in a programmer-facing, debugging context. +/// +/// Generally speaking, you should just `derive` a `Debug` implementation. +/// +/// # Examples +/// +/// Deriving an implementation: +/// +/// ``` +/// use dbg_pls::{pretty, DebugPls}; +/// #[derive(DebugPls)] +/// struct Point { +/// x: i32, +/// y: i32, +/// } +/// +/// let origin = Point { x: 0, y: 0 }; +/// +/// assert_eq!(format!("The origin is: {}", pretty(&origin)), "The origin is: Point { x: 0, y: 0 }"); +/// ``` +/// +/// Manually implementing: +/// +/// ``` +/// use dbg_pls::{pretty, DebugPls, Formatter}; +/// struct Point { +/// x: i32, +/// y: i32, +/// } +/// +/// impl DebugPls for Point { +/// fn fmt(&self, f: Formatter<'_>) { +/// f.debug_struct("Point") +/// .field("x", &self.x) +/// .field("y", &self.y) +/// .finish() +/// } +/// } +/// +/// let origin = Point { x: 0, y: 0 }; +/// +/// assert_eq!(format!("The origin is: {}", pretty(&origin)), "The origin is: Point { x: 0, y: 0 }"); +/// ``` +pub trait DebugPls { + /// Formats the value using the given formatter. + /// + /// # Examples + /// + /// ``` + /// use dbg_pls::{pretty, DebugPls, Formatter}; + /// + /// struct Position { + /// longitude: f32, + /// latitude: f32, + /// } + /// + /// impl DebugPls for Position { + /// fn fmt(&self, f: Formatter<'_>) { + /// f.debug_tuple() + /// .field(&self.longitude) + /// .field(&self.latitude) + /// .finish() + /// } + /// } + /// + /// let position = Position { longitude: 1.987, latitude: 2.983 }; + /// assert_eq!(format!("{}", pretty(&position)), "(1.987, 2.983)"); + /// ``` + fn fmt(&self, f: Formatter<'_>); +} + +/// Tool for formatting, used within [`DebugPls`] implementations +pub struct Formatter<'a> { + expr: &'a mut syn::Expr, +} + +impl<'a> Formatter<'a> { + pub(crate) fn process(value: &dyn DebugPls) -> syn::Expr { + let mut expr = syn::Expr::Verbatim(TokenStream2::new()); + value.fmt(Formatter { expr: &mut expr }); + expr + } + + /// Writes a wrap expression into the formatter. + /// This is typically reserved for more advanced uses + pub fn write_expr(self, expr: impl Into) { + *self.expr = expr.into(); + } + + /// Creates a [`DebugStruct`] builder designed to assist with creation of + /// [`DebugPls`] implementations for structs. + /// + /// # Examples + /// + /// ```rust + /// use dbg_pls::{pretty, DebugPls, Formatter}; + /// + /// struct Foo { + /// bar: i32, + /// baz: String, + /// } + /// + /// impl DebugPls for Foo { + /// fn fmt(&self, f: Formatter) { + /// f.debug_struct("Foo") + /// .field("bar", &self.bar) + /// .field("baz", &self.baz) + /// .finish() + /// } + /// } + /// let value = Foo { + /// bar: 10, + /// baz: "Hello World".to_string(), + /// }; + /// assert_eq!( + /// format!("{}", pretty(&value)), + /// "Foo { bar: 10, baz: \"Hello World\" }", + /// ); + /// ``` + #[must_use] + pub fn debug_struct(self, name: &str) -> DebugStruct<'a> { + DebugStruct::new(self, name) + } + + /// Creates a [`DebugTuple`] builder designed to assist with creation of + /// [`DebugPls`] implementations for tuples. + /// + /// # Examples + /// + /// ```rust + /// use dbg_pls::{pretty, DebugPls, Formatter}; + /// + /// struct Foo(i32, String); + /// + /// impl DebugPls for Foo { + /// fn fmt(&self, f: Formatter) { + /// f.debug_tuple() + /// .field(&self.0) + /// .field(&self.1) + /// .finish() + /// } + /// } + /// + /// let value = Foo(10, "Hello".to_string()); + /// assert_eq!(format!("{}", pretty(&value)), "(10, \"Hello\")"); + /// ``` + #[must_use] + pub fn debug_tuple(self) -> DebugTuple<'a> { + DebugTuple::new(self) + } + + /// Creates a [`DebugTupleStruct`] builder designed to assist with creation of + /// [`DebugPls`] implementations for tuple structs. + /// + /// # Examples + /// + /// ```rust + /// use dbg_pls::{pretty, DebugPls, Formatter}; + /// + /// struct Foo(i32, String); + /// + /// impl DebugPls for Foo { + /// fn fmt(&self, f: Formatter) { + /// f.debug_tuple_struct("Foo") + /// .field(&self.0) + /// .field(&self.1) + /// .finish() + /// } + /// } + /// + /// let value = Foo(10, "Hello".to_string()); + /// assert_eq!(format!("{}", pretty(&value)), "Foo(10, \"Hello\")"); + /// ``` + #[must_use] + pub fn debug_tuple_struct(self, name: &str) -> DebugTupleStruct<'a> { + DebugTupleStruct::new(self, name) + } + + /// Creates a [`DebugList`] builder designed to assist with creation of + /// [`DebugPls`] implementations for list-like structures. + /// + /// # Examples + /// + /// ```rust + /// use dbg_pls::{pretty, DebugPls, Formatter}; + /// + /// struct Foo(Vec); + /// + /// impl DebugPls for Foo { + /// fn fmt(&self, f: Formatter<'_>) { + /// f.debug_list().entries(&self.0).finish() + /// } + /// } + /// + /// let value = Foo(vec![10, 11]); + /// assert_eq!(format!("{}", pretty(&value)), "[10, 11]"); + /// ``` + #[must_use] + pub fn debug_list(self) -> DebugList<'a> { + DebugList::new(self) + } + + /// Creates a [`DebugMap`] builder designed to assist with creation of + /// [`DebugPls`] implementations for maps. + /// + /// # Examples + /// + /// ```rust + /// use dbg_pls::{pretty, DebugPls, Formatter}; + /// use std::collections::BTreeMap; + /// + /// struct Foo(BTreeMap); + /// + /// impl DebugPls for Foo { + /// fn fmt(&self, f: Formatter) { + /// f.debug_map().entries(&self.0).finish() + /// } + /// } + /// let mut value = Foo(BTreeMap::from([ + /// ("Hello".to_string(), 5), + /// ("World".to_string(), 10), + /// ])); + /// assert_eq!( + /// format!("{}", pretty(&value)), + /// "{ + /// [\"Hello\"] = 5; + /// [\"World\"] = 10; + /// }", + /// ); + /// ``` + #[must_use] + pub fn debug_map(self) -> DebugMap<'a> { + DebugMap::new(self) + } + + /// Creates a [`DebugSet`] builder designed to assist with creation of + /// [`DebugPls`] implementations for sets. + /// + /// # Examples + /// + /// ```rust + /// use dbg_pls::{pretty, DebugPls, Formatter}; + /// use std::collections::BTreeSet; + /// + /// struct Foo(BTreeSet); + /// + /// impl DebugPls for Foo { + /// fn fmt(&self, f: Formatter) { + /// f.debug_set().entries(&self.0).finish() + /// } + /// } + /// let mut value = Foo(BTreeSet::from([ + /// "Hello".to_string(), + /// "World".to_string(), + /// ])); + /// assert_eq!( + /// format!("{}", pretty(&value)), + /// "{ + /// \"Hello\"; + /// \"World\" + /// }", + /// ); + /// ``` + #[must_use] + pub fn debug_set(self) -> DebugSet<'a> { + DebugSet::new(self) + } + + /// Writes an identifier into the formatter. Useful for unit structs/variants + /// + /// # Examples + /// + /// ```rust + /// use dbg_pls::{pretty, DebugPls, Formatter}; + /// + /// struct Foo; + /// + /// impl DebugPls for Foo { + /// fn fmt(&self, f: Formatter<'_>) { + /// f.debug_ident("Foo"); + /// } + /// } + /// + /// assert_eq!(format!("{}", pretty(&Foo)), "Foo"); + /// ``` + pub fn debug_ident(self, name: &str) { + let path: syn::Path = syn::Ident::new(name, Span::call_site()).into(); + self.write_expr(syn::ExprPath { + attrs: vec![], + qself: None, + path, + }); + } +} + +#[cfg(test)] +mod tests { + use std::collections::{BTreeMap, BTreeSet}; + + use super::*; + + #[derive(DebugPls, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] + #[dbg_pls(crate = "crate")] + pub struct Demo { + foo: i32, + bar: &'static str, + } + + #[test] + fn debug_struct() { + let val = Demo { + foo: 5, + bar: "hello", + }; + assert_eq!(pretty(&val).to_string(), r#"Demo { foo: 5, bar: "hello" }"#); + } + + #[test] + fn debug_struct_big() { + let val = Demo { + foo: 5, + bar: "Hello, world! I am a very long string", + }; + assert_eq!( + pretty(&val).to_string(), + r#"Demo { + foo: 5, + bar: "Hello, world! I am a very long string", +}"# + ); + } + + #[test] + fn debug_nested_struct() { + let mut val = [Demo { + foo: 5, + bar: "hello", + }; 10]; + val[6].bar = "Hello, world! I am a very long string"; + + assert_eq!( + pretty(&val).to_string(), + r#"[ + Demo { foo: 5, bar: "hello" }, + Demo { foo: 5, bar: "hello" }, + Demo { foo: 5, bar: "hello" }, + Demo { foo: 5, bar: "hello" }, + Demo { foo: 5, bar: "hello" }, + Demo { foo: 5, bar: "hello" }, + Demo { + foo: 5, + bar: "Hello, world! I am a very long string", + }, + Demo { foo: 5, bar: "hello" }, + Demo { foo: 5, bar: "hello" }, + Demo { foo: 5, bar: "hello" }, +]"# + ); + } + + #[test] + fn debug_small_set() { + let set = BTreeSet::from([420, 69]); + + assert_eq!( + pretty(&set).to_string(), + r#"{ + 69; + 420 +}"# + ); + } + + #[test] + fn debug_nested_set() { + let set = BTreeSet::from([ + Demo { + foo: 5, + bar: "hello", + }, + Demo { + foo: 5, + bar: "Hello, world! I am a very long string", + }, + ]); + + assert_eq!( + pretty(&set).to_string(), + r#"{ + Demo { + foo: 5, + bar: "Hello, world! I am a very long string", + }; + Demo { foo: 5, bar: "hello" } +}"# + ); + } + + #[test] + fn debug_map() { + let map = BTreeMap::from([("hello", 60), ("Hello, world! I am a very long string", 12)]); + + assert_eq!( + pretty(&map).to_string(), + r#"{ + ["Hello, world! I am a very long string"] = 12; + ["hello"] = 60; +}"# + ); + } + + #[test] + fn debug_nested_map() { + let map = BTreeMap::from([ + ( + Demo { + foo: 5, + bar: "hello", + }, + 60, + ), + ( + Demo { + foo: 5, + bar: "Hello, world! I am a very long string", + }, + 12, + ), + ]); + + assert_eq!( + pretty(&map).to_string(), + r#"{ + [ + Demo { + foo: 5, + bar: "Hello, world! I am a very long string", + }, + ] = 12; + [Demo { foo: 5, bar: "hello" }] = 60; +}"# + ); + } + + #[derive(DebugPls)] + #[dbg_pls(crate = "crate")] + pub struct Generic { + arg: T, + } + + #[test] + fn debug_generic() { + let generic = Generic { arg: "string" }; + assert_eq!(pretty(&generic).to_string(), r#"Generic { arg: "string" }"#); + } + + #[derive(DebugPls)] + #[dbg_pls(crate = "crate")] + pub struct Generic2 { + arg: Wrapped, + } + + pub struct Wrapped(T); + impl DebugPls for Wrapped { + fn fmt(&self, f: Formatter<'_>) { + f.debug_struct("Wrapped").finish_non_exhaustive(); + } + } + + pub struct NonDebug; + + #[test] + fn debug_generic2() { + let generic = Generic { arg: Wrapped(NonDebug) }; + assert_eq!(pretty(&generic).to_string(), r#"Generic { arg: Wrapped {} }"#); + } + + #[derive(DebugPls)] + #[dbg_pls(crate = "crate")] + pub enum Option2 { + Some(T), + None, + Wtf { foo: i32 } + } + + #[test] + fn debug_enum_generic() { + let some = Option2::Some(42); + assert_eq!(pretty(&some).to_string(), r#"Some(42)"#); + + let none = Option2::::None; + assert_eq!(pretty(&none).to_string(), r#"None"#); + + let wtf = Option2::::Wtf { foo: 42 }; + assert_eq!(pretty(&wtf).to_string(), r#"Wtf { foo: 42 }"#); + } +} diff --git a/dbg-pls/src/pretty.rs b/dbg-pls/src/pretty.rs new file mode 100644 index 0000000..953bf38 --- /dev/null +++ b/dbg-pls/src/pretty.rs @@ -0,0 +1,149 @@ +use syn::__private::Span; + +use crate::{DebugPls, Formatter}; + +pub(crate) fn pretty_string(expr: syn::Expr) -> String { + // unparse requires a `syn::File`, so we are forced to wrap + // our expression in some junk. This is equivalent to + // ```rust + // const _: () = { + // #expr + // }; + // ``` + let file = syn::File { + shebang: None, + attrs: vec![], + items: vec![syn::Item::Const(syn::ItemConst { + expr: Box::new(expr), + // junk... + attrs: vec![], + vis: syn::Visibility::Inherited, + const_token: syn::token::Const::default(), + ident: syn::Ident::new("_", Span::call_site()), + colon_token: syn::token::Colon::default(), + ty: Box::new(syn::Type::Tuple(syn::TypeTuple { + paren_token: syn::token::Paren::default(), + elems: syn::punctuated::Punctuated::default(), + })), + eq_token: syn::token::Eq::default(), + semi_token: syn::token::Semi::default(), + })], + }; + let output = prettyplease::unparse(&file); + + // strip out the junk + let output = &output[14..]; + let output = &output[..output.len() - 2]; + textwrap::dedent(output) +} + +/// Implementation detail for the `pretty!` macro +pub struct Str<'a>(pub &'a str); + +impl<'a> std::fmt::Display for Str<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let expr = syn::parse_str(self.0).map_err(|_| std::fmt::Error)?; + f.write_str(&pretty_string(expr)) + } +} + +struct Pretty<'a>(&'a dyn DebugPls); + +impl<'a> std::fmt::Debug for Pretty<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&pretty_string(Formatter::process(self.0))) + } +} + +impl<'a> std::fmt::Display for Pretty<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} + +#[cfg_attr(docsrs, doc(cfg(feature = "pretty")))] +/// Wraps a [`Debug`] type into a [`std::fmt::Debug`] type for use in regular [`format!`] +pub fn pretty(value: &impl DebugPls) -> impl std::fmt::Debug + std::fmt::Display + '_ { + Pretty(value) +} + +#[cfg_attr(docsrs, doc(cfg(feature = "pretty")))] +#[macro_export] +/// Prints and returns the value of a given expression for quick and dirty +/// debugging. Same as [`std::dbg`] +/// +/// An example: +/// +/// ```rust +/// # use dbg_pls::pretty; +/// let a = 2; +/// let b = pretty!(a * 2) + 1; +/// // ^-- prints: [src/main.rs:2] a * 2 = 4 +/// assert_eq!(b, 5); +/// ``` +/// +/// The macro works by using the [`DebugPls`] implementation of the type of +/// the given expression to print the value to [stderr] along with the +/// source location of the macro invocation as well as the source code +/// of the expression. +/// +/// Invoking the macro on an expression moves and takes ownership of it +/// before returning the evaluated expression unchanged. If the type +/// of the expression does not implement `Copy` and you don't want +/// to give up ownership, you can instead borrow with `pretty!(&expr)` +/// for some expression `expr`. +/// +/// The `pretty!` macro works exactly the same in release builds. +/// This is useful when debugging issues that only occur in release +/// builds or when debugging in release mode is significantly faster. +/// +/// Note that the macro is intended as a debugging tool and therefore you +/// should avoid having uses of it in version control for long periods +/// (other than in tests and similar). +/// Debug output from production code is better done with other facilities +/// such as the [`debug!`] macro from the [`log`] crate. +/// +/// [stderr]: https://en.wikipedia.org/wiki/Standard_streams#Standard_error_(stderr) +/// [`debug!`]: https://docs.rs/log/*/log/macro.debug.html +/// [`log`]: https://crates.io/crates/log +macro_rules! pretty { + () => { + ::std::eprintln!("[{}:{}]", ::std::file!(), ::std::line!()) + }; + ($val:expr $(,)?) => { + match $val { + tmp => { + ::std::eprintln!( + "[{}:{}] {} => {}", + ::std::file!(), + ::std::line!(), + $crate::__private::PrettyStr(::std::stringify!($val)), + $crate::pretty(&tmp) + ); + tmp + } + } + }; + ($($val:expr),+ $(,)?) => { + ($($crate::pretty!($val)),+,) + }; +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use crate::pretty; + + #[test] + fn pretty_macro() { + let map = pretty! { + HashMap::from([ + ("hello", 1), + ("world", 2), + ]) + }; + // map is moved through properly + assert_eq!(map, HashMap::from([("hello", 1), ("world", 2),])); + } +} diff --git a/src/lib.rs b/src/lib.rs index ea71efa..d57a06c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,20 +3,36 @@ pub fn foo(parent: Parent<'_>) { } pub fn requires_parent_fulfill_trait(_: impl dbg_pls::DebugPls) {} - -#[derive(dbg_pls::DebugPls)] pub enum Parent<'a> { A(&'a A<'a>), B(&'a B<'a>), } - -#[derive(dbg_pls::DebugPls)] +impl<'a> dbg_pls::DebugPls for Parent<'a> +where + &'a A<'a>: dbg_pls::DebugPls, + &'a B<'a>: dbg_pls::DebugPls, +{ + fn fmt(&self, f: dbg_pls::Formatter<'_>) {} +} pub struct A<'a> { parent: Parent<'a>, } -#[derive(dbg_pls::DebugPls)] +impl<'a> dbg_pls::DebugPls for A<'a> +where + Parent<'a>: dbg_pls::DebugPls, +{ + fn fmt(&self, f: dbg_pls::Formatter<'_>) {} +} + pub struct B<'a> { parent: Parent<'a>, } + +impl<'a> dbg_pls::DebugPls for B<'a> +where + Parent<'a>: dbg_pls::DebugPls, +{ + fn fmt(&self, f: dbg_pls::Formatter<'_>) {} +} \ No newline at end of file