1use crate::protocol::{
2 ADAPTERS_LIST_SUMMARY, CLI_ABOUT, CONNECT_SUMMARY, DISCONNECT_SUMMARY, MESSAGE_LIST_SUMMARY,
3 MESSAGE_READ_SUMMARY, MESSAGE_SEND_SUMMARY, MESSAGE_STOP_SUMMARY, SCHEMA_SUMMARY,
4 SELECTOR_RULES, STATUS_SUMMARY, TRACE_START_SUMMARY, TRACE_STOP_SUMMARY,
5};
6use clap::{ArgAction, Args, Parser, Subcommand};
7
8#[derive(Debug, Parser)]
9#[command(
10 name = "agent-can",
11 version,
12 about = CLI_ABOUT
13)]
14pub struct CliArgs {
15 #[arg(long, global = true, env = "AGENT_CAN_JSON", default_value_t = false)]
16 pub json: bool,
17
18 #[arg(long, hide = true, default_value_t = false)]
19 pub mcp: bool,
20
21 #[command(subcommand)]
22 pub command: Option<Command>,
23}
24
25#[derive(Debug, Subcommand)]
26pub enum Command {
27 #[command(about = ADAPTERS_LIST_SUMMARY)]
28 Adapters(AdaptersArgs),
29 #[command(about = CONNECT_SUMMARY)]
30 Connect(ConnectArgs),
31 #[command(about = DISCONNECT_SUMMARY)]
32 Disconnect,
33 #[command(about = STATUS_SUMMARY)]
34 Status,
35 #[command(about = SCHEMA_SUMMARY, after_help = SELECTOR_RULES)]
36 Schema(SchemaArgs),
37 Message(MessageArgs),
38 Trace(TraceArgs),
39}
40
41#[derive(Debug, Args)]
42pub struct AdaptersArgs {
43 #[command(subcommand)]
44 pub command: AdaptersCommand,
45}
46
47#[derive(Debug, Subcommand)]
48pub enum AdaptersCommand {
49 List,
50}
51
52#[derive(Debug, Args)]
53pub struct ConnectArgs {
54 #[arg(long)]
55 pub adapter: String,
56 #[arg(long)]
57 pub bitrate: u32,
58 #[arg(long)]
59 pub bitrate_data: Option<u32>,
60 #[arg(long, default_value_t = false)]
61 pub fd: bool,
62 #[arg(long = "dbc", value_name = "alias=path", action = ArgAction::Append)]
63 pub dbcs: Vec<String>,
64}
65
66#[derive(Debug, Args)]
67pub struct SchemaArgs {
68 #[arg(long)]
69 pub filter: Option<String>,
70}
71
72#[derive(Debug, Args)]
73pub struct MessageArgs {
74 #[command(subcommand)]
75 pub command: MessageCommand,
76}
77
78#[derive(Debug, Subcommand)]
79pub enum MessageCommand {
80 #[command(about = MESSAGE_LIST_SUMMARY, after_help = SELECTOR_RULES)]
81 List(MessageListArgs),
82 #[command(about = MESSAGE_READ_SUMMARY, after_help = SELECTOR_RULES)]
83 Read(MessageReadArgs),
84 #[command(about = MESSAGE_SEND_SUMMARY, after_help = SELECTOR_RULES)]
85 Send(MessageSendArgs),
86 #[command(about = MESSAGE_STOP_SUMMARY, after_help = SELECTOR_RULES)]
87 Stop(MessageStopArgs),
88}
89
90#[derive(Debug, Args)]
91pub struct MessageListArgs {
92 #[arg(long)]
93 pub filter: Option<String>,
94 #[arg(long, default_value_t = false)]
95 pub allow_raw: bool,
96 #[arg(long, default_value_t = false)]
97 pub include_tx: bool,
98}
99
100#[derive(Debug, Args)]
101pub struct MessageReadArgs {
102 #[arg(long)]
103 pub select: String,
104 #[arg(long)]
105 pub count: Option<usize>,
106 #[arg(long, default_value_t = false)]
107 pub include_tx: bool,
108}
109
110#[derive(Debug, Args)]
111pub struct MessageSendArgs {
112 #[arg(long)]
113 pub target: String,
114 #[arg(long, value_name = "hex-or-json")]
115 pub data: String,
116 #[arg(long)]
117 pub periodicity_ms: Option<u64>,
118}
119
120#[derive(Debug, Args)]
121pub struct MessageStopArgs {
122 #[arg(long)]
123 pub target: String,
124}
125
126#[derive(Debug, Args)]
127pub struct TraceArgs {
128 #[command(subcommand)]
129 pub command: TraceCommand,
130}
131
132#[derive(Debug, Subcommand)]
133pub enum TraceCommand {
134 #[command(about = TRACE_START_SUMMARY)]
135 Start(TraceStartArgs),
136 #[command(about = TRACE_STOP_SUMMARY)]
137 Stop,
138}
139
140#[derive(Debug, Args)]
141pub struct TraceStartArgs {
142 #[arg(long)]
143 pub path: String,
144}
145
146#[cfg(test)]
147mod tests {
148 use super::{CliArgs, Command, MessageCommand};
149 use clap::Parser;
150
151 #[test]
152 fn parses_grouped_connect_command() {
153 let args = CliArgs::parse_from([
154 "agent-can",
155 "connect",
156 "--adapter",
157 "usb1",
158 "--bitrate",
159 "500000",
160 "--dbc",
161 "vehicle=./vehicle.dbc",
162 ]);
163 assert!(matches!(args.command, Some(Command::Connect(_))));
164 }
165
166 #[test]
167 fn parses_grouped_message_command() {
168 let args = CliArgs::parse_from([
169 "agent-can",
170 "message",
171 "send",
172 "--target",
173 "0x123",
174 "--data",
175 "DEADBEEF",
176 ]);
177 assert!(matches!(
178 args.command,
179 Some(Command::Message(super::MessageArgs {
180 command: MessageCommand::Send(_)
181 }))
182 ));
183 }
184}