Skip to main content

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}