1use clap::{Parser, Subcommand};
2use std::path::PathBuf;
3
4#[derive(Parser)]
5#[command(name = "excel-cli")]
6#[command(
7 author,
8 version,
9 about = "Excel CLI for AI, scripting, and terminal users",
10 long_about = None
11)]
12pub struct Cli {
13 #[command(subcommand)]
14 pub command: Commands,
15}
16
17#[derive(Subcommand)]
18pub enum Commands {
19 Inspect {
21 #[command(subcommand)]
22 subcommand: InspectCommands,
23 },
24 Read {
26 #[command(subcommand)]
27 subcommand: ReadCommands,
28 },
29 Check {
31 file: PathBuf,
33
34 #[arg(long)]
36 sheet: Option<String>,
37
38 #[arg(long)]
44 rules: Option<String>,
45
46 #[arg(long, value_enum, default_value = "info")]
52 severity_threshold: SeverityThreshold,
53 },
54 Ui {
56 file: PathBuf,
58 },
59}
60
61#[derive(Subcommand)]
62pub enum InspectCommands {
63 Workbook {
65 file: PathBuf,
67
68 #[arg(long, value_enum, default_value = "json")]
70 format: OutputFormat,
71 },
72 Sheet {
74 file: PathBuf,
76
77 #[arg(long, group = "sheet_target")]
79 sheet: Option<String>,
80
81 #[arg(long, group = "sheet_target")]
83 sheet_index: Option<usize>,
84
85 #[arg(long, value_enum, default_value = "json")]
87 format: OutputFormat,
88 },
89 Sample {
91 file: PathBuf,
93
94 #[arg(long, group = "sheet_target")]
96 sheet: Option<String>,
97
98 #[arg(long, group = "sheet_target")]
100 sheet_index: Option<usize>,
101
102 #[arg(long)]
104 range: Option<String>,
105
106 #[arg(long)]
108 rows: Option<usize>,
109
110 #[arg(long, default_value = "auto")]
112 header_row: String,
113
114 #[arg(long, value_enum, default_value = "json")]
116 format: OutputFormat,
117 },
118 Columns {
120 file: PathBuf,
122
123 #[arg(long)]
125 sheet: String,
126
127 #[arg(long, default_value = "auto")]
129 header_row: String,
130
131 #[arg(long, value_enum, default_value = "json")]
133 format: OutputFormat,
134 },
135 Tables {
137 file: PathBuf,
139
140 #[arg(long)]
142 sheet: String,
143
144 #[arg(long, value_enum, default_value = "json")]
146 format: OutputFormat,
147 },
148}
149
150#[derive(Subcommand)]
151pub enum ReadCommands {
152 Cell {
154 file: PathBuf,
156
157 #[arg(long, group = "sheet_target")]
159 sheet: Option<String>,
160
161 #[arg(long, group = "sheet_target")]
163 sheet_index: Option<usize>,
164
165 #[arg(long)]
167 cell: String,
168
169 #[arg(long, value_enum, default_value = "json")]
171 format: OutputFormat,
172 },
173 Range {
175 file: PathBuf,
177
178 #[arg(long, group = "sheet_target")]
180 sheet: Option<String>,
181
182 #[arg(long, group = "sheet_target")]
184 sheet_index: Option<usize>,
185
186 #[arg(long)]
188 range: String,
189
190 #[arg(long, value_enum, default_value = "json")]
192 format: OutputFormat,
193 },
194 Rows {
196 file: PathBuf,
198
199 #[arg(long, group = "sheet_target")]
201 sheet: Option<String>,
202
203 #[arg(long, group = "sheet_target")]
205 sheet_index: Option<usize>,
206
207 #[arg(long)]
209 range: Option<String>,
210
211 #[arg(long, default_value = "auto")]
213 header_row: String,
214
215 #[arg(long)]
217 select: Option<String>,
218
219 #[arg(long = "filter")]
221 filters: Vec<String>,
222
223 #[arg(long)]
225 limit: Option<usize>,
226
227 #[arg(long)]
229 offset: Option<usize>,
230
231 #[arg(long)]
233 non_empty: bool,
234
235 #[arg(long, value_enum, default_value = "rows")]
237 output_shape: OutputShape,
238
239 #[arg(long, value_enum, default_value = "json")]
241 format: OutputFormat,
242 },
243 Records {
245 file: PathBuf,
247
248 #[arg(long, group = "sheet_target")]
250 sheet: Option<String>,
251
252 #[arg(long, group = "sheet_target")]
254 sheet_index: Option<usize>,
255
256 #[arg(long)]
258 range: Option<String>,
259
260 #[arg(long, default_value = "auto")]
262 header_row: String,
263
264 #[arg(long)]
266 select: Option<String>,
267
268 #[arg(long = "filter")]
270 filters: Vec<String>,
271
272 #[arg(long)]
274 limit: Option<usize>,
275
276 #[arg(long)]
278 offset: Option<usize>,
279
280 #[arg(long)]
282 non_empty: bool,
283
284 #[arg(long, value_enum, default_value = "records")]
286 output_shape: OutputShape,
287
288 #[arg(long, value_enum, default_value = "json")]
290 format: OutputFormat,
291 },
292}
293
294#[derive(Clone, Debug, Default, clap::ValueEnum)]
295pub enum OutputFormat {
296 #[default]
297 Json,
298 Text,
299}
300
301impl OutputFormat {
302 pub fn as_str(&self) -> &str {
303 match self {
304 OutputFormat::Json => "json",
305 OutputFormat::Text => "text",
306 }
307 }
308}
309
310#[derive(Clone, Copy, Debug, Default, clap::ValueEnum, PartialEq, Eq)]
311pub enum OutputShape {
312 #[default]
313 Rows,
314 Records,
315 Jsonl,
316}
317
318impl OutputShape {
319 pub fn as_str(&self) -> &str {
320 match self {
321 OutputShape::Rows => "rows",
322 OutputShape::Records => "records",
323 OutputShape::Jsonl => "jsonl",
324 }
325 }
326}
327
328#[derive(Clone, Copy, Debug, Default, clap::ValueEnum, PartialEq, Eq)]
329pub enum SeverityThreshold {
330 #[default]
331 Info,
332 Warning,
333 Error,
334}
335
336impl SeverityThreshold {
337 pub fn as_str(&self) -> &'static str {
338 match self {
339 SeverityThreshold::Info => "info",
340 SeverityThreshold::Warning => "warning",
341 SeverityThreshold::Error => "error",
342 }
343 }
344}
345
346pub fn resolve_sheet_target(
348 workbook: &crate::excel::Workbook,
349 sheet: &Option<String>,
350 sheet_index: &Option<usize>,
351) -> Result<usize, crate::cli::error::AppError> {
352 use crate::cli::error::AppError;
353
354 if let Some(name) = sheet {
355 workbook
356 .resolve_sheet_by_name(name)
357 .map_err(|e| AppError::TargetNotFound {
358 message: e.to_string(),
359 })
360 } else if let Some(index) = sheet_index {
361 workbook
362 .resolve_sheet_by_index(*index)
363 .map_err(|e| AppError::TargetNotFound {
364 message: e.to_string(),
365 })
366 } else {
367 Err(AppError::InvalidArgs {
368 message: "Either --sheet or --sheet-index must be provided".to_string(),
369 })
370 }
371}