use std::path::PathBuf;
use clap::{ArgAction, ArgGroup, Args, Parser, Subcommand, ValueEnum};
#[derive(Debug, Parser)]
#[command(author, version, about = "Query XLSX/XML/CSV/JSON/JSONL/Markdown datasets with SQL")]
pub(crate) struct Cli {
#[command(subcommand)]
pub(crate) command: Commands,
}
#[derive(Debug, Subcommand)]
pub(crate) enum Commands {
#[command(
after_long_help = "Examples:\n\
qf query --input ./input.xlsx:Sheet1 --sql \"SELECT * FROM table\"\n\
qf query --input ./inventory.xml:Inventory --sql \"SELECT product, price FROM table WHERE active = 1\"\n\
qf query --input ./inventory.csv --sql \"SELECT product, price FROM table WHERE active = 1\"\n\
qf query --input ./inventory.json:Inventory --sql \"SELECT product, price FROM table WHERE active = 1\"\n\
qf query --input ./inventory.jsonl --sql \"SELECT product, price FROM table WHERE active = 1\"\n\
qf query --input ./a.xlsx:Sheet1 --input ./b.csv --sql \"SELECT * FROM table UNION ALL SELECT * FROM table2\"\n\
qf query --input sales=./sales.xlsx:Q1 --input costs=./costs.csv --sql \"SELECT * FROM sales JOIN costs ON sales.id = costs.id\"\n\
qf query --input ./inventory.md --sql \"SELECT product, price FROM table WHERE active = 1\"\n\
qf query --input ./inventory.md:2 --sql \"SELECT product, price FROM table\"\n\
qf query --input ./input.xlsx:Sheet1 --query-file ./query.sql --output ./result.csv --format csv"
)]
Query(QueryCommand),
#[command(
after_long_help = "Examples:\n\
qf tables --input sales=./sales.xlsx:Q1 --input costs=./costs.csv\n\
qf tables --input ./inventory.json:Inventory --format json\n\
qf tables --input ./inventory.xml:Inventory --input ./inventory.csv --format markdown"
)]
Tables(TablesCommand),
#[command(
after_long_help = "Examples:\n\
qf schema --input ./inventory.json:Inventory\n\
qf schema --input sales=./sales.xlsx:Q1 --input costs=./costs.csv --format json\n\
qf schema --input ./inventory.xml:Inventory --format markdown"
)]
Schema(SchemaCommand),
#[command(
after_long_help = "Examples:\n\
qf inspect --input ./inventory.md:2 --sample 5\n\
qf inspect --input sales=./sales.xlsx:Q1 --input costs=./costs.csv --format json\n\
qf inspect --input ./inventory.csv --stats --format markdown"
)]
Inspect(InspectCommand),
}
#[derive(Debug, Args)]
#[command(group(
ArgGroup::new("query_source")
.required(true)
.args(["sql", "query_file"])
))]
pub(crate) struct QueryCommand {
#[arg(
short,
long,
required = true,
action = ArgAction::Append,
value_name = "[NAME=]PATH[:SHEET|KEY]",
long_help = "Input dataset path. Supported formats: .xlsx, .xml, .csv, .json, .jsonl, .md, .markdown.\n\
Optionally prefix with a table name: NAME=PATH (e.g. sales=./sales.xlsx:Q1).\n\
Without a name prefix, tables are named 'table', 'table2', 'table3', ...\n\
For XLSX use PATH:SheetName (or PATH#SheetName) to select a worksheet.\n\
For XML use PATH:TagName (or PATH#TagName) to select a subtree; without sheet/tag the whole file is used.\n\
For JSON use PATH:Key (or PATH#Key) to select a top-level key; without key the whole JSON root is used.\n\
For Markdown use PATH:N (or PATH#N), where N is the 1-based table number; without key the first table is used.\n\
CSV and JSONL do not support sheet/key selection."
)]
pub(crate) input: Vec<String>,
#[arg(
long,
alias = "query",
short = 'q',
conflicts_with = "query_file",
long_help = "SQL query to execute. Use 'table', 'table2', 'table3', ... or explicit table names.\n\
The flag --query is also accepted as an alias."
)]
pub(crate) sql: Option<String>,
#[arg(long = "query-file", conflicts_with = "sql")]
pub(crate) query_file: Option<PathBuf>,
#[arg(long = "param", value_name = "NAME=VALUE", action = ArgAction::Append)]
pub(crate) params: Vec<String>,
#[arg(short, long)]
pub(crate) output: Option<PathBuf>,
#[arg(long, value_enum)]
pub(crate) format: Option<OutputFormat>,
#[arg(long = "no-headers")]
pub(crate) no_headers: bool,
}
#[derive(Debug, Args)]
pub(crate) struct TablesCommand {
#[arg(
short,
long,
required = true,
action = ArgAction::Append,
value_name = "[NAME=]PATH[:SHEET|KEY]",
long_help = "Input dataset path. Supported formats: .xlsx, .xml, .csv, .json, .jsonl, .md, .markdown.\n\
Optionally prefix with a table name: NAME=PATH (e.g. sales=./sales.xlsx:Q1).\n\
Without a name prefix, tables are named 'table', 'table2', 'table3', ...\n\
For XLSX use PATH:SheetName to select a worksheet.\n\
For XML use PATH:TagName to select a subtree.\n\
For JSON use PATH:Key to select a top-level key.\n\
For Markdown use PATH:N for the 1-based table index."
)]
pub(crate) input: Vec<String>,
#[arg(
short,
long,
long_help = "Default sheet/tag/key applied to every --input that does not already specify one."
)]
pub(crate) sheet: Option<String>,
#[arg(long, value_enum, default_value = "text")]
pub(crate) format: InspectionFormat,
}
#[derive(Debug, Args)]
pub(crate) struct SchemaCommand {
#[arg(
short,
long,
required = true,
action = ArgAction::Append,
value_name = "[NAME=]PATH[:SHEET|KEY]",
long_help = "Input dataset path. Supported formats: .xlsx, .xml, .csv, .json, .jsonl, .md, .markdown.\n\
Optionally prefix with a table name: NAME=PATH (e.g. sales=./sales.xlsx:Q1).\n\
Without a name prefix, tables are named 'table', 'table2', 'table3', ...\n\
For XLSX use PATH:SheetName to select a worksheet.\n\
For XML use PATH:TagName to select a subtree.\n\
For JSON use PATH:Key to select a top-level key.\n\
For Markdown use PATH:N for the 1-based table index."
)]
pub(crate) input: Vec<String>,
#[arg(
short,
long,
long_help = "Default sheet/tag/key applied to every --input that does not already specify one."
)]
pub(crate) sheet: Option<String>,
#[arg(long, value_enum, default_value = "text")]
pub(crate) format: InspectionFormat,
}
#[derive(Debug, Args)]
pub(crate) struct InspectCommand {
#[arg(
short,
long,
required = true,
action = ArgAction::Append,
value_name = "[NAME=]PATH[:SHEET|KEY]",
long_help = "Input dataset path. Supported formats: .xlsx, .xml, .csv, .json, .jsonl, .md, .markdown.\n\
Optionally prefix with a table name: NAME=PATH (e.g. sales=./sales.xlsx:Q1).\n\
Without a name prefix, tables are named 'table', 'table2', 'table3', ...\n\
For XLSX use PATH:SheetName to select a worksheet.\n\
For XML use PATH:TagName to select a subtree.\n\
For JSON use PATH:Key to select a top-level key.\n\
For Markdown use PATH:N for the 1-based table index."
)]
pub(crate) input: Vec<String>,
#[arg(
short,
long,
long_help = "Default sheet/tag/key applied to every --input that does not already specify one."
)]
pub(crate) sheet: Option<String>,
#[arg(long, value_enum, default_value = "text")]
pub(crate) format: InspectionFormat,
#[arg(long, default_value = "5")]
pub(crate) sample: usize,
#[arg(long)]
pub(crate) stats: bool,
}
#[derive(Clone, Debug, ValueEnum)]
pub(crate) enum OutputFormat {
Text,
Csv,
Jsonl,
Markdown,
Xlsx,
Xml,
}
#[derive(Clone, Debug, ValueEnum)]
pub(crate) enum InspectionFormat {
Text,
Json,
Markdown,
}