hedl_cli/commands/inspect.rs
1// Dweve HEDL - Hierarchical Entity Data Language
2//
3// Copyright (c) 2025 Dweve IP B.V. and individual contributors.
4//
5// SPDX-License-Identifier: Apache-2.0
6//
7// Licensed under the Apache License, Version 2.0 (the "License");
8// you may not use this file except in compliance with the License.
9// You may obtain a copy of the License in the LICENSE file at the
10// root of this repository or at: http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Inspect command - HEDL structure visualization
19
20use super::read_file;
21use crate::error::CliError;
22use colored::Colorize;
23use hedl_core::{parse, Item, Value};
24use std::collections::BTreeMap;
25
26/// Inspect and visualize the internal structure of a HEDL file.
27///
28/// Parses a HEDL file and displays its internal structure in a human-readable,
29/// tree-like format with color highlighting. Useful for debugging and understanding
30/// HEDL document organization.
31///
32/// # Arguments
33///
34/// * `file` - Path to the HEDL file to inspect
35/// * `verbose` - If `true`, shows detailed field values and schema information
36///
37/// # Returns
38///
39/// Returns `Ok(())` on success.
40///
41/// # Errors
42///
43/// Returns `Err` if:
44/// - The file cannot be read
45/// - The file contains syntax errors
46///
47/// # Examples
48///
49/// ```no_run
50/// use hedl_cli::commands::inspect;
51///
52/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
53/// // Inspect with basic output
54/// inspect("example.hedl", false)?;
55///
56/// // Inspect with verbose details (shows all fields and schemas)
57/// inspect("example.hedl", true)?;
58/// # Ok(())
59/// # }
60/// ```
61///
62/// # Output
63///
64/// Displays:
65/// - HEDL version
66/// - Struct definitions (type names and columns)
67/// - Alias definitions
68/// - Nest relationships (parent > child)
69/// - Root document structure (tree view)
70/// - In verbose mode: field values, schemas, and row details
71pub fn inspect(file: &str, verbose: bool) -> Result<(), CliError> {
72 let content = read_file(file)?;
73
74 let doc =
75 parse(content.as_bytes()).map_err(|e| CliError::parse(format!("Parse error: {e}")))?;
76
77 println!("{}", "HEDL Document".bold().underline());
78 println!();
79 println!("{} {}.{}", "Version:".cyan(), doc.version.0, doc.version.1);
80
81 if !doc.structs.is_empty() {
82 println!();
83 println!("{}", "Structs:".cyan());
84 for (name, cols) in &doc.structs {
85 println!(" {}: [{}]", name.green(), cols.join(", "));
86 }
87 }
88
89 if !doc.aliases.is_empty() {
90 println!();
91 println!("{}", "Aliases:".cyan());
92 for (name, value) in &doc.aliases {
93 println!(" %{}: \"{}\"", name.green(), value);
94 }
95 }
96
97 if !doc.nests.is_empty() {
98 println!();
99 println!("{}", "Nests:".cyan());
100 for (parent, children) in &doc.nests {
101 for child in children {
102 println!(" {} > {}", parent.green(), child);
103 }
104 }
105 }
106
107 println!();
108 println!("{}", "Root:".cyan());
109 print_items(&doc.root, 1, verbose);
110
111 Ok(())
112}
113
114fn print_items(items: &BTreeMap<String, Item>, indent: usize, verbose: bool) {
115 let prefix = " ".repeat(indent);
116
117 for (key, item) in items {
118 match item {
119 Item::Scalar(value) => {
120 println!("{}{}: {}", prefix, key.yellow(), format_value(value));
121 }
122 Item::Object(child) => {
123 println!("{}{}:", prefix, key.yellow());
124 print_items(child, indent + 1, verbose);
125 }
126 Item::List(list) => {
127 println!(
128 "{}{}:@{} ({} rows)",
129 prefix,
130 key.yellow(),
131 list.type_name.green(),
132 list.rows.len()
133 );
134 if verbose {
135 println!("{} schema: [{}]", prefix, list.schema.join(", "));
136 for (i, row) in list.rows.iter().enumerate() {
137 println!("{} [{}] id={}", prefix, i, row.id);
138 for (field_idx, col) in list.schema.iter().enumerate() {
139 if let Some(v) = row.fields.get(field_idx) {
140 println!("{} {}: {}", prefix, col, format_value(v));
141 }
142 }
143 }
144 }
145 }
146 }
147 }
148}
149
150fn format_value(value: &Value) -> String {
151 match value {
152 Value::Null => "~".dimmed().to_string(),
153 Value::Bool(b) => b.to_string().magenta().to_string(),
154 Value::Int(n) => n.to_string().cyan().to_string(),
155 Value::Float(f) => f.to_string().cyan().to_string(),
156 Value::String(s) => format!("\"{s}\""),
157 Value::Tensor(t) => format!("{t:?}").cyan().to_string(),
158 Value::Reference(r) => r.to_ref_string().green().to_string(),
159 Value::Expression(e) => format!("$({e})").yellow().to_string(),
160 Value::List(items) => {
161 let formatted: Vec<String> = items.iter().map(format_value).collect();
162 format!("({})", formatted.join(", ")).cyan().to_string()
163 }
164 }
165}