query-forge 0.1.0

Run SQL queries on XLSX/XML/CSV/JSON/JSONL/Markdown inputs and export results as text, CSV, JSONL, Markdown, XML, or XLSX
Documentation
use std::path::PathBuf;

use clap::{ArgAction, ArgGroup, Parser, ValueEnum};

#[derive(Debug, Parser)]
#[command(author, version, about = "Query XLSX/XML/CSV/JSON/JSONL/Markdown datasets with SQL")]
#[command(
    after_long_help = "Examples:\n\
    qf --input ./input.xlsx:Sheet1 --query \"SELECT * FROM sheet\"\n\
    qf --input ./inventory.xml:Inventory --query \"SELECT product, price FROM sheet WHERE active = 1\"\n\
    qf --input ./inventory.csv --query \"SELECT product, price FROM sheet WHERE active = 1\"\n\
    qf --input ./inventory.json:Inventory --query \"SELECT product, price FROM sheet WHERE active = 1\"\n\
    qf --input ./inventory.jsonl --query \"SELECT product, price FROM sheet WHERE active = 1\"\n\
    qf --input ./a.xlsx:Sheet1 --input ./b.csv --query \"SELECT * FROM sheet UNION ALL SELECT * FROM sheet2\"\n\
    qf --input ./inventory.md --query \"SELECT product, price FROM sheet WHERE active = 1\"\n\
    qf --input ./inventory.md:2 --query \"SELECT product, price FROM sheet\"\n\
    qf --input ./input.xlsx --sheet Sheet1 --query-file ./query.sql --output ./result.csv --format csv"
)]
#[command(group(
    ArgGroup::new("query_source")
        .required(true)
        .args(["query", "query_file"])
))]
pub(crate) struct Cli {
    #[arg(
        short,
        long,
        required = true,
        action = ArgAction::Append,
        value_name = "PATH[:SHEET|KEY]",
        long_help = "Input dataset path. Supported formats: .xlsx, .xml, .csv, .json, .jsonl, .md, .markdown.\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 in the document; without key the first table is used.\n\
CSV and JSONL do not support sheet/key selection."
    )]
    pub(crate) input: Vec<String>,
    #[arg(
        short,
        long,
        long_help = "Default sheet/tag/key applied to every --input that does not already specify one.\n\
Works with XLSX (worksheet), XML (tag), JSON (top-level key), and Markdown (1-based table number).\n\
Not supported for CSV and JSONL inputs."
    )]
    pub(crate) sheet: Option<String>,
    #[arg(short, long, conflicts_with = "query_file")]
    pub(crate) query: Option<String>,
    #[arg(long = "query-file", conflicts_with = "query")]
    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(Clone, Debug, ValueEnum)]
pub(crate) enum OutputFormat {
    Text,
    Csv,
    Jsonl,
    Markdown,
    Xlsx,
    Xml,
}