diff --git a/Cargo.lock b/Cargo.lock index 69736c1..97a97b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,183 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "anstream" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" + +[[package]] +name = "anstyle-parse" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "colorchoice" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + +[[package]] +name = "similar" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" + +[[package]] +name = "snapbox" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40e14d10e4c2b4331ac24c33baa5a03e1fbca81c045b285b53b2a612d28569fb" +dependencies = [ + "anstream", + "anstyle", + "normalize-line-endings", + "similar", + "snapbox-macros", +] + +[[package]] +name = "snapbox-macros" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f4c14672714436c09254801c934b203196a51182a5107fb76591c7cc56424d" +dependencies = [ + "anstream", +] + [[package]] name = "termtree" version = "0.4.1" +dependencies = [ + "snapbox", +] + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml index dcc59a3..c34816f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -116,6 +116,7 @@ pre-release-replacements = [ [dependencies] [dev-dependencies] +snapbox = "0.6.10" [lints] workspace = true diff --git a/src/lib.rs b/src/lib.rs index a009cca..c232582 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,7 @@ pub struct Tree { pub root: D, pub leaves: Vec>, multiline: bool, - glyphs: GlyphPalette, + glyphs: Option, } impl Tree { @@ -25,7 +25,7 @@ impl Tree { root, leaves: Vec::new(), multiline: false, - glyphs: GlyphPalette::new(), + glyphs: None, } } @@ -42,7 +42,7 @@ impl Tree { /// Customize the rendering of this node pub fn with_glyphs(mut self, glyphs: GlyphPalette) -> Self { - self.glyphs = glyphs; + self.glyphs = Some(glyphs); self } } @@ -56,7 +56,7 @@ impl Tree { /// Customize the rendering of this node pub fn set_glyphs(&mut self, glyphs: GlyphPalette) -> &mut Self { - self.glyphs = glyphs; + self.glyphs = Some(glyphs); self } } @@ -92,25 +92,27 @@ impl Display for Tree { writeln!(f)?; let mut queue = DisplauQueue::new(); let no_space = Rc::new(Vec::new()); - enqueue_leaves(&mut queue, self, no_space); - while let Some((last, leaf, spaces)) = queue.pop_front() { + let default_glyphs = GlyphPalette::new(); + let glyphs = self.glyphs.as_ref().unwrap_or(&default_glyphs); + enqueue_leaves(&mut queue, self, glyphs, no_space); + while let Some((last, leaf, glyphs, spaces)) = queue.pop_front() { let mut prefix = ( if last { - leaf.glyphs.last_item + glyphs.last_item } else { - leaf.glyphs.middle_item + glyphs.middle_item }, - leaf.glyphs.item_indent, + glyphs.item_indent, ); if leaf.multiline { let rest_prefix = ( if last { - leaf.glyphs.last_skip + glyphs.last_skip } else { - leaf.glyphs.middle_skip + glyphs.middle_skip }, - leaf.glyphs.skip_indent, + glyphs.skip_indent, ); debug_assert_eq!(prefix.0.chars().count(), rest_prefix.0.chars().count()); debug_assert_eq!(prefix.1.chars().count(), rest_prefix.1.chars().count()); @@ -123,13 +125,8 @@ impl Display for Tree { for line in root.lines() { // print single line for s in spaces.as_slice() { - if *s { - self.glyphs.last_skip.fmt(f)?; - self.glyphs.skip_indent.fmt(f)?; - } else { - self.glyphs.middle_skip.fmt(f)?; - self.glyphs.skip_indent.fmt(f)?; - } + s.skip.fmt(f)?; + s.indent.fmt(f)?; } prefix.0.fmt(f)?; prefix.1.fmt(f)?; @@ -140,13 +137,8 @@ impl Display for Tree { } else { // print single line for s in spaces.as_slice() { - if *s { - self.glyphs.last_skip.fmt(f)?; - self.glyphs.skip_indent.fmt(f)?; - } else { - self.glyphs.middle_skip.fmt(f)?; - self.glyphs.skip_indent.fmt(f)?; - } + s.skip.fmt(f)?; + s.indent.fmt(f)?; } prefix.0.fmt(f)?; prefix.1.fmt(f)?; @@ -156,30 +148,42 @@ impl Display for Tree { // recurse if !leaf.leaves.is_empty() { - let s: &Vec = &spaces; + let s: &Vec = &spaces; let mut child_spaces = s.clone(); - child_spaces.push(last); + child_spaces.push(if last { + glyphs.last_space() + } else { + glyphs.middle_space() + }); let child_spaces = Rc::new(child_spaces); - enqueue_leaves(&mut queue, leaf, child_spaces); + enqueue_leaves(&mut queue, leaf, glyphs, child_spaces); } } Ok(()) } } -type DisplauQueue<'t, D> = VecDeque<(bool, &'t Tree, Rc>)>; +type DisplauQueue<'t, D> = VecDeque<(bool, &'t Tree, &'t GlyphPalette, Rc>)>; fn enqueue_leaves<'t, D: Display>( queue: &mut DisplauQueue<'t, D>, parent: &'t Tree, - spaces: Rc>, + parent_glyphs: &'t GlyphPalette, + spaces: Rc>, ) { for (i, leaf) in parent.leaves.iter().rev().enumerate() { let last = i == 0; - queue.push_front((last, leaf, spaces.clone())); + let glyphs = leaf.glyphs.as_ref().unwrap_or(parent_glyphs); + queue.push_front((last, leaf, glyphs, spaces.clone())); } } +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +struct SpacePalette { + skip: &'static str, + indent: &'static str, +} + #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct GlyphPalette { pub middle_item: &'static str, @@ -203,6 +207,20 @@ impl GlyphPalette { skip_indent: " ", } } + + fn middle_space(&self) -> SpacePalette { + SpacePalette { + skip: self.middle_skip, + indent: self.skip_indent, + } + } + + fn last_space(&self) -> SpacePalette { + SpacePalette { + skip: self.last_skip, + indent: self.skip_indent, + } + } } impl Default for GlyphPalette { diff --git a/src/tests.rs b/src/tests.rs index 30f5221..ff3e7f0 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,32 +1,45 @@ +use snapbox::assert_data_eq; +use snapbox::str; + use super::*; #[test] fn render_tree_root() { let tree = Tree::new("foo"); - assert_eq!(format!("{}", tree), "foo\n"); + assert_data_eq!( + format!("{}", tree), + str![[r#" +foo + +"#]] + ); } #[test] fn render_tree_with_leaves() { let tree = Tree::new("foo").with_leaves([Tree::new("bar").with_leaves(["baz"])]); - assert_eq!( + assert_data_eq!( format!("{}", tree), - r#"foo + str![[r#" +foo └── bar └── baz -"# + +"#]] ); } #[test] fn render_tree_with_multiple_leaves() { let tree = Tree::new("foo").with_leaves(["bar", "baz"]); - assert_eq!( + assert_data_eq!( format!("{}", tree), - r#"foo + str![[r#" +foo ├── bar └── baz -"# + +"#]] ); } @@ -36,13 +49,75 @@ fn render_tree_with_multiline_leaf() { Tree::new("hello\nworld").with_multiline(true), Tree::new("goodbye\nworld").with_multiline(true), ]); - assert_eq!( + assert_data_eq!( format!("{}", tree), - r#"foo + str![[r#" +foo ├── hello │ world └── goodbye world -"# + +"#]] + ); +} + +#[test] +fn render_custom_glyphs() { + let root = GlyphPalette { + middle_item: "[mid ]", + last_item: "[last ]", + item_indent: "[indent ]", + + middle_skip: "[mskip]", + last_skip: "[lskip]", + skip_indent: "[iskip ]", + }; + let middle = GlyphPalette { + middle_item: "(mid )", + last_item: "(last )", + item_indent: "(indent )", + + middle_skip: "(mskip)", + last_skip: "(lskip)", + skip_indent: "(iskip )", + }; + + let tree = Tree::new("node 1").with_glyphs(root).with_leaves([ + Tree::new("node 1.1"), + Tree::new("node 1.2"), + Tree::new("node 1.3").with_leaves([ + Tree::new("node 1.3.1").with_glyphs(middle), + Tree::new("node 1.3.2").with_glyphs(middle), + Tree::new("node 1.3.3") + .with_glyphs(middle) + .with_leaves(["node 1.3.3.1", "node 1.3.3.2"]), + ]), + Tree::new("node 1.4").with_leaves([ + Tree::new("node 1.4.1"), + Tree::new("node 1.4.2"), + Tree::new("node 1.4.3").with_leaves(["node 1.4.3.1", "node 1.4.3.2"]), + ]), + ]); + assert_data_eq!( + format!("{}", tree), + str![[r#" +node 1 +[mid ][indent ]node 1.1 +[mid ][indent ]node 1.2 +[mid ][indent ]node 1.3 +[mskip][iskip ](mid )(indent )node 1.3.1 +[mskip][iskip ](mid )(indent )node 1.3.2 +[mskip][iskip ](last )(indent )node 1.3.3 +[mskip][iskip ](lskip)(iskip )(mid )(indent )node 1.3.3.1 +[mskip][iskip ](lskip)(iskip )(last )(indent )node 1.3.3.2 +[last ][indent ]node 1.4 +[lskip][iskip ][mid ][indent ]node 1.4.1 +[lskip][iskip ][mid ][indent ]node 1.4.2 +[lskip][iskip ][last ][indent ]node 1.4.3 +[lskip][iskip ][lskip][iskip ][mid ][indent ]node 1.4.3.1 +[lskip][iskip ][lskip][iskip ][last ][indent ]node 1.4.3.2 + +"#]] ); }