Skip to main content

dampen_cli/commands/
inspect.rs

1#![allow(clippy::print_stderr, clippy::print_stdout)]
2
3//! Inspect command - view IR and generated code
4
5use dampen_core::{HandlerSignature, generate_application, parse};
6use std::fs;
7
8#[derive(clap::Args)]
9pub struct InspectArgs {
10    /// Path to the .dampen file to inspect
11    #[arg(short, long)]
12    file: String,
13
14    /// Show generated Rust code instead of IR
15    #[arg(long)]
16    codegen: bool,
17
18    /// Output format: human (default) or json
19    #[arg(long, default_value = "human")]
20    format: String,
21
22    /// Model struct name (used with --codegen)
23    #[arg(long, default_value = "Model")]
24    model: String,
25
26    /// Message enum name (used with --codegen)
27    #[arg(long, default_value = "Message")]
28    message: String,
29
30    /// Handler names (for codegen validation, comma-separated)
31    #[arg(long, value_delimiter = ',', num_args = 1..)]
32    handlers: Vec<String>,
33}
34
35pub fn execute(args: &InspectArgs) -> Result<(), String> {
36    // Read the file
37    let content = fs::read_to_string(&args.file)
38        .map_err(|e| format!("Failed to read file '{}': {}", args.file, e))?;
39
40    // Parse the XML
41    let document = parse(&content).map_err(|e| format!("Parse error: {}", e))?;
42
43    if args.codegen {
44        // Generate code
45        let handler_signatures: Vec<HandlerSignature> = args
46            .handlers
47            .iter()
48            .map(|name| {
49                HandlerSignature {
50                    name: name.clone(),
51                    param_type: None, // Could be enhanced to parse type info
52                    returns_command: false,
53                }
54            })
55            .collect();
56
57        let output =
58            generate_application(&document, &args.model, &args.message, &handler_signatures)
59                .map_err(|e| format!("Code generation error: {}", e))?;
60
61        match args.format.as_str() {
62            "json" => {
63                let json = serde_json::json!({
64                    "code": output.code,
65                    "warnings": output.warnings,
66                });
67                let json_str = serde_json::to_string_pretty(&json)
68                    .map_err(|e| format!("JSON serialization error: {}", e))?;
69                println!("{}", json_str);
70            }
71            "human" => {
72                println!("// Generated Rust code from: {}\n", args.file);
73                if !output.warnings.is_empty() {
74                    println!("// Warnings:");
75                    for warning in &output.warnings {
76                        println!("//   - {}", warning);
77                    }
78                    println!();
79                }
80                println!("{}", output.code);
81            }
82            _ => return Err(format!("Unknown format: {}", args.format)),
83        }
84    } else {
85        // Show IR tree
86        match args.format.as_str() {
87            "json" => {
88                let json = serde_json::to_string_pretty(&document)
89                    .map_err(|e| format!("JSON serialization error: {}", e))?;
90                println!("{}", json);
91            }
92            "human" => {
93                print_ir_tree(&document, 0);
94            }
95            _ => return Err(format!("Unknown format: {}", args.format)),
96        }
97    }
98
99    Ok(())
100}
101
102/// Print IR tree in human-readable format
103fn print_ir_tree(doc: &dampen_core::DampenDocument, indent: usize) {
104    let prefix = "  ".repeat(indent);
105
106    println!("{}DampenDocument {{", prefix);
107    println!(
108        "{}  version: v{}.{}",
109        prefix, doc.version.major, doc.version.minor
110    );
111    println!("{}  root:", prefix);
112    print_widget_node(&doc.root, indent + 2);
113    println!("{}}}", prefix);
114}
115
116fn print_widget_node(node: &dampen_core::WidgetNode, indent: usize) {
117    let prefix = "  ".repeat(indent);
118
119    println!("{}WidgetNode {{", prefix);
120    println!("{}  kind: {:?}", prefix, node.kind);
121
122    if let Some(id) = &node.id {
123        println!("{}  id: Some({:?})", prefix, id);
124    }
125
126    // Print attributes
127    if !node.attributes.is_empty() {
128        println!("{}  attributes: {{", prefix);
129        for (key, value) in &node.attributes {
130            print!("{}    {:?}: ", prefix, key);
131            print_attribute_value(value);
132            println!();
133        }
134        println!("{}  }}", prefix);
135    }
136
137    // Print events
138    if !node.events.is_empty() {
139        println!("{}  events: [", prefix);
140        for event in &node.events {
141            println!(
142                "{}    {:?} -> {} (line {}, col {})",
143                prefix, event.event, event.handler, event.span.line, event.span.column
144            );
145        }
146        println!("{}  ]", prefix);
147    }
148
149    // Print children
150    if !node.children.is_empty() {
151        println!("{}  children: [", prefix);
152        for child in &node.children {
153            print_widget_node(child, indent + 2);
154        }
155        println!("{}  ]", prefix);
156    }
157
158    println!(
159        "{}  span: line {}, column {}",
160        prefix, node.span.line, node.span.column
161    );
162    println!("{}}}", prefix);
163}
164
165fn print_attribute_value(value: &dampen_core::AttributeValue) {
166    match value {
167        dampen_core::AttributeValue::Static(s) => {
168            print!("Static({:?})", s);
169        }
170        dampen_core::AttributeValue::Binding(expr) => {
171            print!("Binding(");
172            print_expr(&expr.expr);
173            print!(")");
174        }
175        dampen_core::AttributeValue::Interpolated(parts) => {
176            print!("Interpolated([");
177            for (i, part) in parts.iter().enumerate() {
178                if i > 0 {
179                    print!(", ");
180                }
181                match part {
182                    dampen_core::InterpolatedPart::Literal(s) => {
183                        print!("Literal({:?})", s);
184                    }
185                    dampen_core::InterpolatedPart::Binding(expr) => {
186                        print!("Binding(");
187                        print_expr(&expr.expr);
188                        print!(")");
189                    }
190                }
191            }
192            print!("])");
193        }
194    }
195}
196
197fn print_expr(expr: &dampen_core::Expr) {
198    match expr {
199        dampen_core::Expr::FieldAccess(fa) => {
200            print!("FieldAccess({})", fa.path.join("."));
201        }
202        dampen_core::Expr::MethodCall(mc) => {
203            print!("MethodCall(");
204            print_expr(&mc.receiver);
205            print!(".{}(", mc.method);
206            for (i, arg) in mc.args.iter().enumerate() {
207                if i > 0 {
208                    print!(", ");
209                }
210                print_expr(arg);
211            }
212            print!("))");
213        }
214        dampen_core::Expr::BinaryOp(bo) => {
215            print!("BinaryOp(");
216            print_expr(&bo.left);
217            print!(" {:?} ", bo.op);
218            print_expr(&bo.right);
219            print!(")");
220        }
221        dampen_core::Expr::UnaryOp(uo) => {
222            print!("UnaryOp({:?} ", uo.op);
223            print_expr(&uo.operand);
224            print!(")");
225        }
226        dampen_core::Expr::Conditional(ce) => {
227            print!("Conditional(");
228            print_expr(&ce.condition);
229            print!(" then ");
230            print_expr(&ce.then_branch);
231            print!(" else ");
232            print_expr(&ce.else_branch);
233            print!(")");
234        }
235        dampen_core::Expr::Literal(lit) => match lit {
236            dampen_core::LiteralExpr::String(s) => print!("Literal({:?})", s),
237            dampen_core::LiteralExpr::Integer(i) => print!("Literal({})", i),
238            dampen_core::LiteralExpr::Float(f) => print!("Literal({})", f),
239            dampen_core::LiteralExpr::Bool(b) => print!("Literal({})", b),
240        },
241        dampen_core::Expr::SharedFieldAccess(sa) => {
242            print!("SharedFieldAccess(shared.{})", sa.path.join("."));
243        }
244    }
245}