use {
super::Bluegum,
crate::{
Nodes,
Printer,
},
std::ops::Deref,
};
enum Tree {
Node {
name: String,
value: String,
nodes: Vec<Tree>,
other_nodes: Vec<OtherTree>,
},
Fragment {
nodes: Vec<Tree>,
other_nodes: Vec<OtherTree>,
},
Leaf {
name: String,
value: String,
},
}
impl Bluegum for Tree {
fn node(&self, b: &mut crate::Builder) {
match self {
| Tree::Node {
name,
value,
nodes,
other_nodes,
} => {
b.name("Tree::Node")
.field("name", name)
.field("value", value)
.add_nodes("nodes", nodes)
.add_nodes("other_nodes", other_nodes)
},
| Tree::Fragment { nodes, other_nodes } => {
b.name("Tree::Fragment")
.field("nodes", "nodes")
.add_nodes("nodes", nodes)
.add_nodes("other_nodes", other_nodes)
},
| Tree::Leaf { name, value } => {
b.name("Tree::Leaf")
.field("name", name)
.field("value", value)
},
};
}
}
enum OtherTree {
Node {
other_name: String,
other_value: String,
nodes: Vec<Tree>,
},
Fragment {
nodes: Vec<Tree>,
},
Leaf {
other_name: String,
other_value: String,
},
}
impl Bluegum for OtherTree {
fn node(&self, b: &mut crate::Builder) {
match self {
| OtherTree::Node {
other_name,
other_value,
nodes,
} => {
b.name("OtherTree::OtherNode")
.field("other_name", other_name)
.field("other_value", other_value)
.add_nodes("nodes", nodes)
},
| OtherTree::Fragment { nodes } => {
b.name("OtherTree::OtherFragment").add_nodes("nodes", nodes)
},
| OtherTree::Leaf {
other_name,
other_value,
} => {
b.name("OtherTree::OtherLeaf")
.field("other_name", other_name)
.field("other_value", other_value)
},
};
}
}
fn basic_tree() -> Tree {
Tree::Node {
name: "r0".to_string(),
value: "_".to_string(),
nodes: vec![Tree::Node {
name: "r0.c0".to_string(),
value: "_".to_string(),
nodes: vec![Tree::Node {
name: "r0.c0.c0".to_string(),
value: "_".to_string(),
nodes: vec![Tree::Leaf {
name: "r0.c0.c0.c0".to_string(),
value: "_".to_string(),
}],
other_nodes: vec![OtherTree::Leaf {
other_name: "r0.c0.c0.o0".to_string(),
other_value: "_".to_string(),
}],
}],
other_nodes: vec![OtherTree::Leaf {
other_name: "r0.c0.o0".to_string(),
other_value: "_".to_string(),
}],
}],
other_nodes: vec![
OtherTree::Leaf {
other_name: "r0.o0".to_string(),
other_value: "_".to_string(),
},
OtherTree::Leaf {
other_name: "r0.o1".to_string(),
other_value: "_".to_string(),
},
OtherTree::Node {
other_name: "r0.02".to_string(),
other_value: "_".to_string(),
nodes: vec![Tree::Node {
name: "r0.02.c0".to_string(),
value: "_".to_string(),
nodes: vec![
Tree::Leaf {
name: "r0.02.c0.c0".to_string(),
value: "_".to_string(),
},
Tree::Leaf {
name: "r0.02.c0.c1".to_string(),
value: "_".to_string(),
},
Tree::Leaf {
name: "r0.02.c0.c2".to_string(),
value: "_".to_string(),
},
],
other_nodes: vec![
OtherTree::Node {
other_name: "r0.02.o0".to_string(),
other_value: "has no nodes".to_string(),
nodes: vec![],
},
OtherTree::Leaf {
other_name: "r0.02.o1".to_string(),
other_value: "_".to_string(),
},
OtherTree::Leaf {
other_name: "r0.02.o2".to_string(),
other_value: "_".to_string(),
},
],
}],
},
OtherTree::Leaf {
other_name: "r0.o3".to_string(),
other_value: "_".to_string(),
},
],
}
}
#[test]
fn basic_bluegum() {
let tree = basic_tree();
let mut p = Printer::default();
p.render(&tree);
insta::with_settings!(
{snapshot_path => std::path::PathBuf::from(".snapshots")},
{insta::assert_snapshot!(p.strip_color())}
);
}
#[test]
fn basic_bluegum_verbose() {
let tree = basic_tree();
let mut p = Printer::default();
p.render(&tree);
p.to_stdout();
insta::with_settings!(
{snapshot_path => std::path::PathBuf::from(".snapshots")},
{ insta::assert_snapshot!(p.strip_color()) });
}
enum Items<'src> {
ImportStatement(ImportStatement),
Expression(Expression<'src>),
Block(Vec<Items<'src>>),
}
impl<'src> Bluegum for Items<'src> {
fn node(&self, b: &mut crate::Builder) {
match self {
| Items::ImportStatement(imports) => {
b.name("Item::ImportStatement")
.add_nodes("items", &imports.items)
},
| Items::Expression(expr) => {
b.name("Item::Expression").add_node("expr", expr)
},
| Items::Block(block) => b.name("Item::Block").add_nodes("block", block),
};
}
}
struct ImportStatement {
items: Vec<String>,
}
impl Bluegum for ImportStatement {
fn node(&self, b: &mut crate::Builder) {
b.name("ImportStatement").add_nodes("items", &self.items);
}
}
enum Expression<'src> {
Literal(&'src str),
Call(&'src str, Vec<Expression<'src>>),
Binary(Box<Expression<'src>>, Box<Expression<'src>>),
}
impl Bluegum for Expression<'_> {
fn node(&self, b: &mut crate::Builder) {
match self {
| Expression::Literal(val) => {
b.name("Expression::Literal").field("val", val);
},
| Expression::Call(ident, block) => {
b.name("Expression::Call")
.field("ident", ident)
.add_nodes("block", block);
},
| Expression::Binary(left, right) => {
b.name("Expression::Binary")
.add_nodes("left", left)
.add_nodes("right", right);
},
}
}
}
#[test]
fn test_ast() {
let tree = Items::Block(vec![
Items::ImportStatement(ImportStatement {
items: vec!["foo".to_string(), "bar".to_string()],
}),
Items::Expression(Expression::Call("foo", vec![
Expression::Literal("1"),
Expression::Literal("2"),
Expression::Binary(
Box::new(Expression::Literal("3")),
Box::new(Expression::Literal("4")),
),
])),
]);
let mut p = Printer::default();
p.render(&tree);
p.to_stdout();
insta::with_settings!(
{snapshot_path => std::path::PathBuf::from(".snapshots")},
{ insta::assert_snapshot!(p.strip_color()) });
}
#[test]
fn test_builder_methods() {
use crate::Builder;
struct TestNode;
impl crate::Bluegum for TestNode {
fn node(&self, b: &mut crate::Builder) {
b.name("TestNode")
.field("name", "example")
.field("count", 42)
.option_field("optional_present", Some("value"))
.option_field("optional_missing", None::<String>)
.alt("alternate_info")
.debug("debug_key", "debug_value")
.option_debug("debug_optional", Some("debug_present"))
.option_debug("debug_missing", None::<String>)
.divider()
.field("after_divider", "test");
}
}
let node = TestNode;
let mut p = Printer::default();
p.render(&node);
let output = p.strip_color();
assert!(output.contains("TestNode"));
assert!(output.contains("name:") && output.contains("example"));
assert!(output.contains("count:") && output.contains("42"));
assert!(output.contains("optional_present:") && output.contains("value"));
assert!(!output.contains("optional_missing"));
assert!(output.contains("alternate_info"));
assert!(output.contains("after_divider:") && output.contains("test"));
}
#[test]
fn test_primitive_types() {
let primitives = vec![
crate::Builder::render(&42u32),
crate::Builder::render(&"hello".to_string()),
crate::Builder::render(&true),
crate::Builder::render(&std::f64::consts::PI),
crate::Builder::render(&'c'),
];
for builder in primitives {
let mut p = Printer::default();
p.render_builder(builder);
let output = p.strip_color();
assert!(!output.is_empty());
}
}
#[test]
fn test_collections() {
let vec_node = vec!["a".to_string(), "b".to_string(), "c".to_string()];
let tuple_node = ("first".to_string(), 42u32);
let mut p = Printer::default();
p.render(&vec_node);
let vec_output = p.strip_color();
assert!(vec_output.contains("Vec"));
assert!(vec_output.contains("items"));
let mut p = Printer::default();
p.render(&tuple_node);
let tuple_output = p.strip_color();
assert!(tuple_output.contains("(T1,T2)"));
assert!(tuple_output.contains("T1"));
assert!(tuple_output.contains("T2"));
}
#[test]
fn test_empty_collections() {
let empty_vec: Vec<String> = vec![];
let mut p = Printer::default();
p.render(&empty_vec);
let output = p.strip_color();
assert!(output.contains("Vec"));
assert!(output.contains("items"));
}
#[test]
fn test_leaf_helper() {
let leaf = crate::Leaf::new("MyLeaf".to_string(), &[
("field1", "value1".to_string()),
("field2", "value2".to_string()),
]);
let mut p = Printer::default();
p.render(&leaf);
let output = p.strip_color();
assert!(output.contains("MyLeaf"));
assert!(output.contains("field1: value1"));
assert!(output.contains("field2: value2"));
}
#[test]
fn test_printer_styles() {
struct SimpleNode;
impl crate::Bluegum for SimpleNode {
fn node(&self, b: &mut crate::Builder) {
b.name("SimpleNode").field("test", "value");
}
}
let node = SimpleNode;
let mut styles = crate::Styles {
hide_line_numbers: true,
..Default::default()
};
let mut p = Printer::new(styles);
p.render(&node);
let output_no_lines = p.strip_color();
assert!(!output_no_lines.starts_with("00│"));
let mut styles = crate::Styles {
hide_lines: true,
..Default::default()
};
let mut p = Printer::new(styles);
p.render(&node);
let output_no_tree_lines = p.strip_color();
assert!(output_no_tree_lines.contains("SimpleNode"));
}
#[test]
fn test_printer_title() {
struct SimpleNode;
impl crate::Bluegum for SimpleNode {
fn node(&self, b: &mut crate::Builder) {
b.name("SimpleNode");
}
}
let node = SimpleNode;
let mut p = Printer::default().set_title("My Tree");
p.render(&node);
let output = p.strip_color();
assert!(output.contains("My Tree"));
}
#[test]
fn test_complex_nested_structure() {
struct ComplexNode {
name: String,
children: Vec<ComplexNode>,
metadata: Option<String>,
}
impl crate::Bluegum for ComplexNode {
fn node(&self, b: &mut crate::Builder) {
b.name("ComplexNode")
.field("name", &self.name)
.option_field("metadata", self.metadata.as_ref())
.add_nodes("children", &self.children);
}
}
let root = ComplexNode {
name: "root".to_string(),
metadata: Some("root_meta".to_string()),
children: vec![
ComplexNode {
name: "child1".to_string(),
metadata: None,
children: vec![ComplexNode {
name: "grandchild".to_string(),
metadata: Some("grandchild_meta".to_string()),
children: vec![],
}],
},
ComplexNode {
name: "child2".to_string(),
metadata: Some("child2_meta".to_string()),
children: vec![],
},
],
};
let mut p = Printer::default();
p.render(&root);
let output = p.strip_color();
assert!(output.contains("root"));
assert!(output.contains("child1"));
assert!(output.contains("child2"));
assert!(output.contains("grandchild"));
assert!(output.contains("root_meta"));
assert!(output.contains("child2_meta"));
assert!(output.contains("grandchild_meta"));
assert!(!output.contains("None"));
}
#[test]
fn test_bluegum_with_state() {
struct SymbolTable {
symbols: Vec<String>,
}
struct Variable {
symbol_id: usize,
value: i32,
}
impl crate::Bluegum for Variable {
fn node(&self, b: &mut crate::Builder) {
b.name("Variable")
.field("symbol_id", self.symbol_id)
.field("value", self.value);
}
}
impl crate::BluegumWithState<SymbolTable> for Variable {
fn node_with_state(&self, b: &mut crate::Builder, symbols: &SymbolTable) {
b.name("Variable")
.field("name", &symbols.symbols[self.symbol_id])
.alt(format!("id:{}", self.symbol_id))
.field("value", self.value);
}
}
let symbols = SymbolTable {
symbols: vec!["x".to_string(), "y".to_string(), "result".to_string()],
};
let var = Variable {
symbol_id: 0,
value: 42,
};
let mut p = Printer::default();
p.render(&var);
let output_no_state = p.strip_color();
assert!(output_no_state.contains("symbol_id: 0"));
assert!(!output_no_state.contains("name: x"));
let mut p = Printer::default();
let builder_with_state = crate::Builder::render_with_state(&var, &symbols);
p.render_builder(builder_with_state);
let output_with_state = p.strip_color();
assert!(
output_with_state.contains("name:") && output_with_state.contains("x")
);
assert!(output_with_state.contains("id:0"));
assert!(
output_with_state.contains("value:") && output_with_state.contains("42")
);
}
#[test]
fn test_bluegum_with_state_collections() {
struct Context {
prefix: String,
}
struct Item {
name: String,
}
impl crate::Bluegum for Item {
fn node(&self, b: &mut crate::Builder) {
b.name("Item").field("name", &self.name);
}
}
impl crate::BluegumWithState<Context> for Item {
fn node_with_state(&self, b: &mut crate::Builder, ctx: &Context) {
b.name("Item")
.field("full_name", format!("{}{}", ctx.prefix, self.name));
}
}
struct Container {
items: Vec<Item>,
}
impl crate::Bluegum for Container {
fn node(&self, b: &mut crate::Builder) {
b.name("Container").add_nodes("items", &self.items);
}
}
impl crate::BluegumWithState<Context> for Container {
fn node_with_state(&self, b: &mut crate::Builder, ctx: &Context) {
b.name("Container").add_nodes_with_state::<Context>(
ctx,
"items",
&self.items,
);
}
}
let container = Container {
items: vec![
Item {
name: "first".to_string(),
},
Item {
name: "second".to_string(),
},
],
};
let context = Context {
prefix: "test_".to_string(),
};
let mut p = Printer::default();
let builder_with_state =
crate::Builder::render_with_state(&container, &context);
p.render_builder(builder_with_state);
let output = p.strip_color();
assert!(output.contains("Container"));
assert!(output.contains("test_first"));
assert!(output.contains("test_second"));
}
#[test]
fn test_convenience_functions() {
struct TestNode;
impl crate::Bluegum for TestNode {
fn node(&self, b: &mut crate::Builder) {
b.name("TestNode");
}
}
let node = TestNode;
let output = crate::render(&node);
assert!(output.contains("TestNode"));
assert!(!output.contains('\x1b')); }
#[test]
fn test_node_types() {
enum NodeType {
Node {
name: String,
children: Vec<NodeType>,
},
Fragment {
children: Vec<NodeType>,
},
Leaf {
value: String,
},
}
impl crate::Bluegum for NodeType {
fn node(&self, b: &mut crate::Builder) {
match self {
| NodeType::Node { name, children } => {
b.name("Node")
.field("name", name)
.add_nodes("children", children);
},
| NodeType::Fragment { children } => {
b.name("Fragment").add_nodes("children", children);
},
| NodeType::Leaf { value } => {
b.name("Leaf").field("value", value);
},
}
}
}
let tree = NodeType::Node {
name: "root".to_string(),
children: vec![
NodeType::Fragment {
children: vec![
NodeType::Leaf {
value: "leaf1".to_string(),
},
NodeType::Leaf {
value: "leaf2".to_string(),
},
],
},
NodeType::Leaf {
value: "leaf3".to_string(),
},
],
};
let mut p = Printer::default();
p.render(&tree);
let output = p.strip_color();
assert!(output.contains("Node"));
assert!(output.contains("Fragment"));
assert!(output.contains("Leaf"));
assert!(output.contains("leaf1"));
assert!(output.contains("leaf2"));
assert!(output.contains("leaf3"));
}
#[test]
fn test_mixed_builder_usage() {
struct MixedNode;
impl crate::Bluegum for MixedNode {
fn node(&self, b: &mut crate::Builder) {
let child1 = crate::Builder::render(&"child1".to_string());
let child2 = crate::Builder::render(&42);
b.name("MixedNode")
.field("type", "mixed")
.add_named_builder("string_child", child1)
.add_named_builder("number_child", child2)
.add_builders(vec![
crate::Builder::render(&true),
crate::Builder::render(&std::f32::consts::PI),
]);
}
}
let node = MixedNode;
let mut p = Printer::default();
p.render(&node);
let output = p.strip_color();
assert!(output.contains("MixedNode"));
assert!(output.contains("type: mixed"));
assert!(output.contains("string_child"));
assert!(output.contains("number_child"));
assert!(output.contains("child1"));
assert!(output.contains("42"));
assert!(output.contains("true"));
assert!(output.contains("3.14"));
}
#[cfg(feature = "url")]
#[test]
fn test_url_support() {
use url::Url;
let url =
Url::parse("https://user:pass@example.com:8080/path?query=value#fragment")
.unwrap();
let mut p = Printer::default();
p.render(&url);
let output = p.strip_color();
dbg!(&output);
assert!(output.contains("Url"));
assert!(output.contains("scheme: https"));
assert!(output.contains("username: user"));
assert!(output.contains("password: pass"));
assert!(output.contains("host: example.com"));
assert!(output.contains("port: 8080"));
assert!(output.contains("path: /path"));
assert!(output.contains("query: query=value"));
assert!(output.contains("fragment: fragment"));
}
#[test]
fn test_edge_cases() {
struct EdgeCaseNode;
impl crate::Bluegum for EdgeCaseNode {
fn node(&self, b: &mut crate::Builder) {
b.name("") .field("", "empty_key") .field("key", "") .alt("") .debug("", ""); }
}
let node = EdgeCaseNode;
let mut p = Printer::default();
p.render(&node);
let output = p.strip_color();
assert!(!output.is_empty());
}
#[test]
fn test_long_content() {
struct LongContentNode;
impl crate::Bluegum for LongContentNode {
fn node(&self, b: &mut crate::Builder) {
let long_string = "a".repeat(1000);
b.name("LongContentNode")
.field("long_field", &long_string)
.alt(&long_string)
.debug("long_debug", &long_string);
}
}
let node = LongContentNode;
let mut p = Printer::default();
p.render(&node);
let output = p.strip_color();
assert!(output.contains("LongContentNode"));
assert!(!output.is_empty());
assert!(output.len() > 1000); }
#[test]
fn test_advanced_features() {
struct Database {
tables: Vec<String>,
relationships: Vec<(usize, usize, String)>,
metadata: std::collections::HashMap<usize, String>,
}
impl crate::Bluegum for Database {
fn node(&self, b: &mut crate::Builder) {
b.name("Database")
.debug("version", "2.0")
.debug("engine", "PostgreSQL")
.field("table_count", self.tables.len())
.alt("production");
for (i, table_name) in self.tables.iter().enumerate() {
let mut table_builder = crate::Builder::new();
table_builder
.name("Table")
.field("name", table_name)
.alt(format!("table_{i}")) .debug("id", i)
.debug("schema", "public");
if let Some(meta) = self.metadata.get(&i) {
table_builder
.field("metadata", meta)
.debug("source", "config");
}
let related_tables: Vec<String> = self
.relationships
.iter()
.filter(|(from, ..)| *from == i)
.map(|(_, to, rel_type)| {
format!("{} -> {}", rel_type, self.tables[*to])
})
.collect();
if !related_tables.is_empty() {
for (j, relation) in related_tables.iter().enumerate() {
let mut rel_builder = crate::Builder::new();
rel_builder
.name("Relationship")
.field("type", relation)
.alt("FK") .debug("index", j)
.debug("enforced", "true");
table_builder
.add_named_builder(&format!("relation_{j}"), rel_builder);
}
}
b.add_named_builder(&format!("table_{i}"), table_builder);
}
}
}
let mut metadata = std::collections::HashMap::new();
metadata.insert(0, "Primary user table".to_string());
metadata.insert(1, "Order tracking".to_string());
let db = Database {
tables: vec![
"users".to_string(),
"orders".to_string(),
"products".to_string(),
],
relationships: vec![
(1, 0, "belongs_to".to_string()), (1, 2, "has_many".to_string()), ],
metadata,
};
let mut p = Printer::default();
p.render(&db);
let output = p.strip_color();
assert!(output.contains("Database")); assert!(output.contains("production")); assert!(output.contains("[version: 2.0]")); assert!(output.contains("[engine: PostgreSQL]")); assert!(output.contains("Table")); assert!(output.contains("table_0")); assert!(output.contains("belongs_to -> users")); assert!(output.contains("FK")); assert!(output.contains("[enforced: true]")); assert!(output.contains("Primary user table")); }