Skip to main content

agent_sim/cli/
output.rs

1use crate::protocol::{Response, ResponseData};
2use comfy_table::{ContentArrangement, Table, presets::UTF8_HORIZONTAL_ONLY};
3use serde_json::json;
4
5pub fn print_response(response: &Response, json_mode: bool) {
6    if json_mode {
7        if let Some(ResponseData::WatchSamples { samples }) = &response.data {
8            for sample in samples {
9                let line = json!({
10                    "tick": sample.tick,
11                    "time_us": sample.time_us,
12                    "name": sample.signal,
13                    "value": sample.value
14                });
15                println!(
16                    "{}",
17                    serde_json::to_string(&line).unwrap_or_else(|_| "{}".to_string())
18                );
19            }
20            return;
21        }
22        println!(
23            "{}",
24            serde_json::to_string(response).unwrap_or_else(|_| {
25                "{\"success\":false,\"error\":\"failed to serialize response\"}".to_string()
26            })
27        );
28        return;
29    }
30
31    if !response.success {
32        eprintln!("{}", response.error.as_deref().unwrap_or("unknown error"));
33        return;
34    }
35    match &response.data {
36        Some(ResponseData::Ack) => println!("ok"),
37        Some(ResponseData::Loaded {
38            libpath,
39            signal_count,
40        }) => {
41            println!("Loaded: {libpath}");
42            println!("Signals: {signal_count}");
43        }
44        Some(ResponseData::ProjectInfo {
45            libpath,
46            tick_duration_us,
47            signal_count,
48        }) => {
49            println!("Loaded: true");
50            println!("Project: {libpath}");
51            println!("Tick(us): {tick_duration_us}");
52            println!("Signals: {signal_count}");
53        }
54        Some(ResponseData::Signals { signals }) => {
55            let mut table = Table::new();
56            table
57                .load_preset(UTF8_HORIZONTAL_ONLY)
58                .set_content_arrangement(ContentArrangement::Dynamic)
59                .set_header(vec!["ID", "Name", "Type", "Units"]);
60            for signal in signals {
61                table.add_row(vec![
62                    signal.id.to_string(),
63                    signal.name.clone(),
64                    signal.signal_type.to_string(),
65                    signal.units.clone().unwrap_or_else(|| "-".to_string()),
66                ]);
67            }
68            println!("{table}");
69        }
70        Some(ResponseData::SignalValues { values }) => {
71            let mut table = Table::new();
72            table
73                .load_preset(UTF8_HORIZONTAL_ONLY)
74                .set_header(vec!["ID", "Name", "Type", "Value", "Units"]);
75            for signal in values {
76                table.add_row(vec![
77                    signal.id.to_string(),
78                    signal.name.clone(),
79                    signal.signal_type.to_string(),
80                    format!("{:?}", signal.value),
81                    signal.units.clone().unwrap_or_else(|| "-".to_string()),
82                ]);
83            }
84            println!("{table}");
85        }
86        Some(ResponseData::SignalSample {
87            tick,
88            time_us,
89            values,
90        }) => {
91            println!("Tick: {tick}");
92            println!("Time(us): {time_us}");
93            let mut table = Table::new();
94            table
95                .load_preset(UTF8_HORIZONTAL_ONLY)
96                .set_header(vec!["ID", "Name", "Type", "Value", "Units"]);
97            for signal in values {
98                table.add_row(vec![
99                    signal.id.to_string(),
100                    signal.name.clone(),
101                    signal.signal_type.to_string(),
102                    format!("{:?}", signal.value),
103                    signal.units.clone().unwrap_or_else(|| "-".to_string()),
104                ]);
105            }
106            println!("{table}");
107        }
108        Some(ResponseData::SetResult { writes_applied }) => {
109            println!("Writes applied: {writes_applied}");
110        }
111        Some(ResponseData::TimeStatus {
112            state,
113            elapsed_ticks,
114            elapsed_time_us,
115            speed,
116        }) => {
117            println!(
118                "State: {:?}  Ticks: {}  Sim-time: {:.6}s  Speed: {}x",
119                state,
120                elapsed_ticks,
121                *elapsed_time_us as f64 / 1_000_000.0,
122                speed
123            );
124        }
125        Some(ResponseData::TimeAdvanced {
126            requested_us,
127            advanced_ticks,
128            advanced_us,
129        }) => {
130            println!(
131                "Requested: {}us  Advanced: {} ticks ({}us)",
132                requested_us, advanced_ticks, advanced_us
133            );
134        }
135        Some(ResponseData::Speed { speed }) => println!("{speed}"),
136        Some(ResponseData::CanBuses { buses }) => {
137            let mut table = Table::new();
138            table.load_preset(UTF8_HORIZONTAL_ONLY).set_header(vec![
139                "ID",
140                "Bus",
141                "Bitrate",
142                "Data Bitrate",
143                "FD",
144                "Attached",
145            ]);
146            for bus in buses {
147                table.add_row(vec![
148                    bus.id.to_string(),
149                    bus.name.clone(),
150                    bus.bitrate.to_string(),
151                    if bus.bitrate_data == 0 {
152                        "-".to_string()
153                    } else {
154                        bus.bitrate_data.to_string()
155                    },
156                    bus.fd_capable.to_string(),
157                    bus.attached_iface
158                        .clone()
159                        .unwrap_or_else(|| "-".to_string()),
160                ]);
161            }
162            println!("{table}");
163        }
164        Some(ResponseData::CanSend { bus, arb_id, len }) => {
165            println!("Sent frame on {bus}: id=0x{arb_id:X} len={len}");
166        }
167        Some(ResponseData::CanInspect { bus, frames }) => {
168            println!("Bus: {bus}");
169            let mut table = Table::new();
170            table
171                .load_preset(UTF8_HORIZONTAL_ONLY)
172                .set_header(vec!["Arb ID", "Len", "Flags", "Data"]);
173            for frame in frames {
174                table.add_row(vec![
175                    format!("0x{:X}", frame.arb_id),
176                    frame.len.to_string(),
177                    format!("0x{:02X}", frame.flags),
178                    frame.data_hex.clone(),
179                ]);
180            }
181            println!("{table}");
182        }
183        Some(ResponseData::CanSchedules { schedules }) => {
184            let mut table = Table::new();
185            table.load_preset(UTF8_HORIZONTAL_ONLY).set_header(vec![
186                "Job",
187                "Bus",
188                "Arb ID",
189                "Data",
190                "Flags",
191                "Every (ticks)",
192                "Next due",
193                "Enabled",
194            ]);
195            for schedule in schedules {
196                table.add_row(vec![
197                    schedule.job_id.clone(),
198                    schedule.bus.clone(),
199                    format!("0x{:X}", schedule.arb_id),
200                    schedule.data_hex.clone(),
201                    format!("0x{:02X}", schedule.flags),
202                    schedule.every_ticks.to_string(),
203                    schedule.next_due_tick.to_string(),
204                    schedule.enabled.to_string(),
205                ]);
206            }
207            println!("{table}");
208        }
209        Some(ResponseData::DbcLoaded { bus, signal_count }) => {
210            println!("Loaded DBC for {bus}: {signal_count} signals");
211        }
212        Some(ResponseData::SharedChannels { channels }) => {
213            let mut table = Table::new();
214            table
215                .load_preset(UTF8_HORIZONTAL_ONLY)
216                .set_header(vec!["ID", "Channel", "Slots"]);
217            for channel in channels {
218                table.add_row(vec![
219                    channel.id.to_string(),
220                    channel.name.clone(),
221                    channel.slot_count.to_string(),
222                ]);
223            }
224            println!("{table}");
225        }
226        Some(ResponseData::SharedValues { channel, slots }) => {
227            println!("Channel: {channel}");
228            let mut table = Table::new();
229            table
230                .load_preset(UTF8_HORIZONTAL_ONLY)
231                .set_header(vec!["Slot", "Type", "Value"]);
232            for slot in slots {
233                table.add_row(vec![
234                    slot.slot_id.to_string(),
235                    slot.signal_type.to_string(),
236                    format!("{:?}", slot.value),
237                ]);
238            }
239            println!("{table}");
240        }
241        Some(ResponseData::WatchSamples { samples }) => {
242            for sample in samples {
243                println!(
244                    "tick={} time_us={} {}={:?}",
245                    sample.tick, sample.time_us, sample.signal, sample.value
246                );
247            }
248        }
249        Some(ResponseData::RecipeResult {
250            recipe,
251            dry_run,
252            steps_executed,
253            steps,
254        }) => {
255            println!("Recipe: {recipe}");
256            println!("Dry run: {dry_run}");
257            println!("Steps: {steps_executed}");
258            for step in steps {
259                let instance = step.instance.as_deref().unwrap_or("-");
260                println!("- [{:?}] {} {}", step.kind, instance, step.detail);
261            }
262        }
263        Some(ResponseData::EnvStatus {
264            env,
265            running,
266            instance_count,
267            tick_duration_us,
268        }) => {
269            println!("Env: {env}");
270            println!("Running: {running}");
271            println!("Instances: {instance_count}");
272            println!("Tick(us): {tick_duration_us}");
273        }
274        Some(ResponseData::InstanceStatus {
275            instance,
276            socket_path,
277            running,
278            env,
279        }) => {
280            println!("Instance: {instance}");
281            println!("Socket: {socket_path}");
282            println!("Running: {running}");
283            println!("Env: {}", env.clone().unwrap_or_else(|| "-".to_string()));
284        }
285        Some(ResponseData::InstanceList { instances }) => {
286            let mut table = Table::new();
287            table
288                .load_preset(UTF8_HORIZONTAL_ONLY)
289                .set_header(vec!["Instance", "Running", "Env", "Socket"]);
290            for item in instances {
291                table.add_row(vec![
292                    item.name.clone(),
293                    item.running.to_string(),
294                    item.env.clone().unwrap_or_else(|| "-".to_string()),
295                    item.socket_path.clone(),
296                ]);
297            }
298            println!("{table}");
299        }
300        None => println!("ok"),
301    }
302}