Skip to content

Commit 280ca4a

Browse files
authored
Rust: Add colored output for parse command and tree_inspect (#770)
<img width="2511" height="1883" alt="CleanShot 2025-11-03 at 02 07 33@2x" src="https://github.com/user-attachments/assets/7d292775-3a32-4e4e-b880-a4708e0828e3" /> <img width="1541" height="1155" alt="CleanShot 2025-11-03 at 02 07 16@2x" src="https://github.com/user-attachments/assets/a108b7dc-76f7-4d74-8a9c-0926c351a1a4" />
1 parent c0ecce1 commit 280ca4a

File tree

7 files changed

+61
-47
lines changed

7 files changed

+61
-47
lines changed

β€Žrust/Cargo.tomlβ€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ path = "src/main.rs"
2121

2222
[dependencies]
2323
libc = "0.2"
24+
colored = "2"
2425

2526
[dev-dependencies]
2627
insta = "1.40"

β€Žrust/Makefileβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ cli: build
2626
@./bin/$(BIN_NAME) || true
2727

2828
test: build
29-
cargo +stable test --verbose -- --test-threads=1
29+
NO_COLOR=1 cargo +stable test --verbose -- --test-threads=1
3030

3131
format:
3232
cargo +nightly fmt

β€Žrust/src/ffi.rsβ€Ž

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
#[repr(C)]
2-
pub struct ParserOptions {
3-
_private: [u8; 0],
4-
}
5-
61
pub use crate::bindings::{
72
ast_node_free, element_source_to_string, hb_array_get, hb_array_size, hb_string_T, herb_extract,
83
herb_free_tokens, herb_lex, herb_parse, herb_prism_version, herb_version, token_type_to_string,

β€Žrust/src/token.rsβ€Ž

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::location::Location;
22
use crate::range::Range;
3+
use colored::*;
34
use std::fmt;
45

56
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -48,7 +49,11 @@ impl Token {
4849
}
4950

5051
pub fn tree_inspect(&self) -> String {
51-
format!("\"{}\" (location: {})", self.escaped_value(), self.location)
52+
format!(
53+
"{} {}",
54+
format!("\"{}\"", self.escaped_value()).green(),
55+
format!("(location: {})", self.location).dimmed()
56+
)
5257
}
5358
}
5459

β€Žrust/tests/error_handling_test.rsβ€Ž

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ fn test_unclosed_element_error() {
66
let result = parse(source).unwrap();
77

88
let tree_inspect = result.tree_inspect();
9-
assert!(tree_inspect.contains("UNCLOSED_ELEMENT_ERROR"));
9+
assert!(tree_inspect.contains("UnclosedElementError"));
1010
assert!(tree_inspect.contains("Tag `<div>` opened at (1:1) was never closed"));
11-
assert!(tree_inspect.contains("MISSING_CLOSING_TAG_ERROR"));
11+
assert!(tree_inspect.contains("MissingClosingTagError"));
1212
assert!(tree_inspect.contains("Opening tag `<div>` at (1:1) doesn't have a matching closing tag"));
1313
}
1414

@@ -18,7 +18,7 @@ fn test_tag_names_mismatch_error() {
1818
let result = parse(source).unwrap();
1919

2020
let tree_inspect = result.tree_inspect();
21-
assert!(tree_inspect.contains("TAG_NAMES_MISMATCH_ERROR"));
21+
assert!(tree_inspect.contains("TagNamesMismatchError"));
2222
assert!(tree_inspect.contains("Opening tag `<div>` at (1:1) closed with `</span>`"));
2323
}
2424

β€Žtemplates/rust/src/errors.rs.erbβ€Ž

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::{Location, Token};
2+
use colored::*;
23

34
fn escape_string(s: &str) -> String {
45
s.replace('\\', "\\\\")
@@ -9,25 +10,25 @@ fn escape_string(s: &str) -> String {
910
}
1011

1112
fn format_string_value(value: &str) -> String {
12-
format!("\"{}\"", escape_string(value))
13+
format!("\"{}\"", escape_string(value)).green().to_string()
1314
}
1415

1516
fn format_token_value(token: &Option<Token>) -> String {
16-
token.as_ref().map(|t| t.tree_inspect()).unwrap_or_else(|| "βˆ…".to_string())
17+
token.as_ref().map(|t| t.tree_inspect()).unwrap_or_else(|| "βˆ…".magenta().to_string())
1718
}
1819

1920
fn format_token_type_value(token_type: &Option<String>) -> String {
20-
token_type.as_ref().map(|t| format!("\"{}\"", t)).unwrap_or_else(|| "βˆ…".to_string())
21+
token_type.as_ref().map(|t| format!("\"{}\"", t).green().to_string()).unwrap_or_else(|| "βˆ…".magenta().to_string())
2122
}
2223

2324
#[allow(dead_code)]
2425
fn format_position_value(position: &Option<crate::Position>) -> String {
25-
position.as_ref().map(|p| p.to_string()).unwrap_or_else(|| "βˆ…".to_string())
26+
position.as_ref().map(|p| p.to_string()).unwrap_or_else(|| "βˆ…".magenta().to_string())
2627
}
2728

2829
#[allow(dead_code)]
2930
fn format_size_value(value: usize) -> String {
30-
value.to_string()
31+
value.to_string().magenta().bold().to_string()
3132
}
3233

3334
#[derive(Debug, Clone, PartialEq)]
@@ -167,22 +168,26 @@ impl <%= error.name %> {
167168
pub fn tree_inspect(&self) -> String {
168169
let mut output = String::new();
169170

170-
output.push_str(&format!("@ <%= error.type %> {}\n", self.location));
171+
output.push_str(&format!("{} {} {}\n",
172+
"@".white(),
173+
"<%= error.name %>".red().bold(),
174+
format!("(location: {})", self.location).dimmed()
175+
));
171176
<%- symbol = error.fields.any? ? "β”œβ”€β”€" : "└──" -%>
172-
output.push_str(&format!("<%= symbol %> message: {}\n", format_string_value(&self.message)));
177+
output.push_str(&format!("{} {}: {}\n", "<%= symbol %>".white(), "message".white(), format_string_value(&self.message)));
173178
<%- error.fields.each do |field| -%>
174179
<%- symbol = error.fields.last == field ? "└──" : "β”œβ”€β”€" -%>
175180
<%- case field -%>
176181
<%- when Herb::Template::StringField -%>
177-
output.push_str(&format!("<%= symbol %> <%= field.name %>: {}\n", format_string_value(&self.<%= field.name %>)));
182+
output.push_str(&format!("{} {}: {}\n", "<%= symbol %>".white(), "<%= field.name %>".white(), format_string_value(&self.<%= field.name %>)));
178183
<%- when Herb::Template::TokenField -%>
179-
output.push_str(&format!("<%= symbol %> <%= field.name %>: {}\n", format_token_value(&self.<%= field.name %>)));
184+
output.push_str(&format!("{} {}: {}\n", "<%= symbol %>".white(), "<%= field.name %>".white(), format_token_value(&self.<%= field.name %>)));
180185
<%- when Herb::Template::TokenTypeField -%>
181-
output.push_str(&format!("<%= symbol %> <%= field.name %>: {}\n", format_token_type_value(&self.<%= field.name %>)));
186+
output.push_str(&format!("{} {}: {}\n", "<%= symbol %>".white(), "<%= field.name %>".white(), format_token_type_value(&self.<%= field.name %>)));
182187
<%- when Herb::Template::PositionField -%>
183-
output.push_str(&format!("<%= symbol %> <%= field.name %>: {}\n", format_position_value(&self.<%= field.name %>)));
188+
output.push_str(&format!("{} {}: {}\n", "<%= symbol %>".white(), "<%= field.name %>".white(), format_position_value(&self.<%= field.name %>)));
184189
<%- when Herb::Template::SizeTField -%>
185-
output.push_str(&format!("<%= symbol %> <%= field.name %>: {}\n", format_size_value(self.<%= field.name %>)));
190+
output.push_str(&format!("{} {}: {}\n", "<%= symbol %>".white(), "<%= field.name %>".white(), format_size_value(self.<%= field.name %>)));
186191
<%- end -%>
187192
<%- end -%>
188193

β€Žtemplates/rust/src/nodes.rs.erbβ€Ž

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::errors::{AnyError, ErrorNode};
22
use crate::{Location, Token};
3+
use colored::*;
34

45
fn escape_string(s: &str) -> String {
56
s.replace('\\', "\\\\")
@@ -10,15 +11,15 @@ fn escape_string(s: &str) -> String {
1011
}
1112

1213
fn format_string_value(value: &str) -> String {
13-
format!("\"{}\"", escape_string(value))
14+
format!("\"{}\"", escape_string(value)).green().to_string()
1415
}
1516

1617
fn format_token_value(token: &Option<Token>) -> String {
17-
token.as_ref().map(|t| t.tree_inspect()).unwrap_or_else(|| "βˆ…".to_string())
18+
token.as_ref().map(|t| t.tree_inspect()).unwrap_or_else(|| "βˆ…".magenta().to_string())
1819
}
1920

2021
fn format_bool_value(value: bool) -> String {
21-
value.to_string()
22+
value.to_string().magenta().bold().to_string()
2223
}
2324

2425
fn format_node_value<T: Node + ?Sized>(node: &Option<Box<T>>, prefix: &str, add_spacing: bool) -> String {
@@ -31,7 +32,7 @@ fn format_node_value<T: Node + ?Sized>(node: &Option<Box<T>>, prefix: &str, add_
3132
}
3233
output
3334
} else {
34-
"βˆ…\n".to_string()
35+
format!("{}\n", "βˆ…".magenta())
3536
}
3637
}
3738

@@ -49,22 +50,24 @@ fn inspect_errors(errors: &[AnyError], prefix: &str) -> String {
4950
}
5051

5152
let mut output = String::new();
52-
output.push_str(&format!("β”œβ”€β”€ errors: ({} error{})\n",
53-
errors.len(),
54-
if errors.len() == 1 { "" } else { "s" }
53+
output.push_str(&format!("{} {}: {}\n",
54+
"β”œβ”€β”€".white(),
55+
"errors".red().bold(),
56+
format!("({} error{})", errors.len(), if errors.len() == 1 { "" } else { "s" }).dimmed()
5557
));
5658

5759
for (i, error) in errors.iter().enumerate() {
5860
let is_last = i == errors.len() - 1;
5961
let symbol = if is_last { "└── " } else { "β”œβ”€β”€ " };
60-
let next_prefix = if is_last { " " } else { "β”‚ " };
62+
let next_prefix_str = if is_last { " " } else { "β”‚ " };
63+
let next_prefix = next_prefix_str.white().to_string();
6164

6265
let tree = error.tree_inspect();
6366
let tree = tree.trim_end_matches('\n');
64-
output.push_str(&format!("{}{}{}\n", prefix, symbol, tree.replace('\n', &format!("\n{}{}", prefix, next_prefix))));
67+
output.push_str(&format!("{}{}{}\n", prefix, symbol.white(), tree.replace('\n', &format!("\n{}{}", prefix, next_prefix))));
6568

6669
if !is_last {
67-
output.push_str(&format!("{}β”‚ \n", prefix));
70+
output.push_str(&format!("{}{}\n", prefix, "β”‚ ".white()));
6871
}
6972
}
7073

@@ -74,28 +77,29 @@ fn inspect_errors(errors: &[AnyError], prefix: &str) -> String {
7477

7578
fn inspect_array(array: &[AnyNode], prefix: &str) -> String {
7679
if array.is_empty() {
77-
return "[]\n".to_string();
80+
return format!("{}\n", "[]".dimmed());
7881
}
7982

8083
let mut output = String::new();
81-
output.push_str(&format!("({} item{})\n", array.len(), if array.len() == 1 { "" } else { "s" }));
84+
output.push_str(&format!("{}\n", format!("({} item{})", array.len(), if array.len() == 1 { "" } else { "s" }).dimmed()));
8285

8386
for (i, item) in array.iter().enumerate() {
8487
let is_last = i == array.len() - 1;
8588
let symbol = if is_last { "└── " } else { "β”œβ”€β”€ " };
86-
let next_prefix = if is_last { " " } else { "β”‚ " };
89+
let next_prefix_str = if is_last { " " } else { "β”‚ " };
90+
let next_prefix = next_prefix_str.white().to_string();
8791

8892
let tree = item.tree_inspect();
8993
let tree = tree.trim_end_matches('\n');
9094

9195
output.push_str(prefix);
92-
output.push_str(symbol);
96+
output.push_str(&symbol.white().to_string());
9397
output.push_str(&tree.replace('\n', &format!("\n{}{}", prefix, next_prefix)));
9498
output.push('\n');
9599

96100
if !is_last {
97101
output.push_str(prefix);
98-
output.push_str(next_prefix);
102+
output.push_str(&next_prefix);
99103
output.push('\n');
100104
}
101105
}
@@ -109,12 +113,12 @@ fn inspect_node_field<T: Node + ?Sized>(node: &T, prefix: &str) -> String {
109113

110114
let lines: Vec<&str> = tree.split('\n').collect();
111115
if lines.is_empty() {
112-
return "βˆ…\n".to_string();
116+
return format!("{}\n", "βˆ…".magenta());
113117
}
114118

115119
let mut result = String::new();
116120
result.push_str(prefix);
117-
result.push_str("└── ");
121+
result.push_str(&"└── ".white().to_string());
118122
result.push_str(lines[0]);
119123
result.push('\n');
120124

@@ -336,27 +340,31 @@ impl Node for <%= node.name %> {
336340
fn tree_inspect(&self) -> String {
337341
let mut output = String::new();
338342

339-
output.push_str(&format!("@ <%= node.name %> (location: {})\n", self.location));
340-
output.push_str(&format_errors_field(&self.errors, <%- if node.fields.any? -%>"β”‚ "<%- else -%>" "<%- end -%>));
343+
output.push_str(&format!("{} {} {}\n",
344+
"@".white(),
345+
"<%= node.name %>".yellow().bold(),
346+
format!("(location: {})", self.location).dimmed()
347+
));
348+
output.push_str(&format_errors_field(&self.errors, &<%- if node.fields.any? -%>"β”‚ "<%- else -%>" "<%- end -%>.white().to_string()));
341349
<%- if node.fields.any? -%>
342350
<%- node.fields.each_with_index do |field, index| -%>
343351
<%- is_last = index == node.fields.length - 1 -%>
344352
<%- symbol = is_last ? "└── " : "β”œβ”€β”€ " -%>
345353
<%- case field -%>
346354
<%- when Herb::Template::StringField, Herb::Template::ElementSourceField -%>
347-
output.push_str(&format!("<%= symbol %><%= field.name %>: {}\n", format_string_value(&self.<%= field.name %>)));
355+
output.push_str(&format!("{}{}: {}\n", "<%= symbol %>".white(), "<%= field.name %>".white(), format_string_value(&self.<%= field.name %>)));
348356
<%- when Herb::Template::TokenField -%>
349-
output.push_str(&format!("<%= symbol %><%= field.name %>: {}\n", format_token_value(&self.<%= field.name %>)));
357+
output.push_str(&format!("{}{}: {}\n", "<%= symbol %>".white(), "<%= field.name %>".white(), format_token_value(&self.<%= field.name %>)));
350358
<%- when Herb::Template::BooleanField -%>
351-
output.push_str(&format!("<%= symbol %><%= field.name %>: {}\n", format_bool_value(self.<%= field.name %>)));
359+
output.push_str(&format!("{}{}: {}\n", "<%= symbol %>".white(), "<%= field.name %>".white(), format_bool_value(self.<%= field.name %>)));
352360
<%- when Herb::Template::ArrayField -%>
353-
output.push_str(&format!("<%= symbol %><%= field.name %>: {}", format_array_value(&self.<%= field.name %>, "<%= is_last ? " " : "β”‚ " %>")));
361+
output.push_str(&format!("{}{}: {}", "<%= symbol %>".white(), "<%= field.name %>".white(), format_array_value(&self.<%= field.name %>, &"<%= is_last ? " " : "β”‚ " %>".white().to_string())));
354362
<%- when Herb::Template::NodeField -%>
355-
output.push_str(&format!("<%= symbol %><%= field.name %>: {}", format_node_value(&self.<%= field.name %>, "<%= is_last ? " " : "β”‚ " %>", <%= !is_last %>)));
363+
output.push_str(&format!("{}{}: {}", "<%= symbol %>".white(), "<%= field.name %>".white(), format_node_value(&self.<%= field.name %>, &"<%= is_last ? " " : "β”‚ " %>".white().to_string(), <%= !is_last %>)));
356364
<%- end -%>
357365
<%- end -%>
358366
<%- else -%>
359-
output.push_str("└── (no fields)\n");
367+
output.push_str(&format!("{} {}\n", "└──".white(), "(no fields)".dimmed()));
360368
<%- end -%>
361369

362370
output

0 commit comments

Comments
Β (0)