query-forge 0.9.0

Run SQL queries and dataset diffs on XLSX/XML/CSV/JSON/JSONL/Markdown/HTML/Feather/Parquet inputs and export results as text, CSV, JSONL, Markdown, XML, HTML, XLSX, Feather, or Parquet
Documentation
pub(crate) const QUERY_AFTER_LONG_HELP: &str = "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 ./inventory.feather --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 --sql-file ./query.sql --output ./result.csv --format csv\n\
    qf query --input ./summary.json --sql \"SELECT key, value FROM table\" --json-mode object\n\
    qf query --input ./record.json --sql \"SELECT \\\"user.name\\\", \\\"address.city\\\" FROM table\" --json-mode flatten\n\
    qf query --input ./config.xml --sql \"SELECT tag, value FROM table WHERE tag = 'timeout'\" --xml-mode descendants\n\
    qf query --input ./items.xml --sql \"SELECT id, type, value FROM table\" --xml-mode attributes\n\
    qf query --input '@clipboard|csv' --sql \"SELECT product, price FROM table WHERE active = 1\" --output '@clipboard|json' --format json\n\
    qf query --input './data.txt|csv' --sql \"SELECT * FROM table\"\n\
    qf query --input './inventory.xlsx#Main|xlsx' --sql \"SELECT * FROM table\"\n\
    qf query --input ./inventory.csv --with 'active AS (SELECT * FROM table WHERE status = 1)' --sql 'SELECT category, COUNT(*) FROM active GROUP BY category'\n\
    qf query --input ./inventory.csv --sql-file ./multistep.sql\n\
\n\
Workflow hints:\n\
    Start with qf tables / qf schema when inputs are unfamiliar.\n\
    Use qf inspect --stats --sample N for fast profiling before writing SQL.\n\
    Use qf diff for snapshot reconciliation and qf pivot for quick cross-tab reports.\n\
\n\
AI mode:\n\
    Add --ai-mode (global flag) to wrap output in a JSON envelope.\n\
    Example: qf --ai-mode query --input ./inventory.csv --sql \"SELECT * FROM table\"\n\
    The envelope includes: data, metadata, warnings, and structured error fields.\n\
\n\
Query source:\n\
    Provide SQL inline with --sql or load it from disk with --sql-file.\n\
    --sql-file supports multiple `;`-separated statements; the result of the last SELECT is returned.\n\
\n\
CTE chaining (--with):\n\
    Use --with 'name AS (...)' to prepend CTE definitions before the main query.\n\
    Repeat the flag for multiple CTEs. The keyword WITH is added automatically.\n\
    Example: --with 'recent AS (SELECT * FROM table WHERE year = 2024)' \\\n\
             --sql 'SELECT category, COUNT(*) FROM recent GROUP BY category'\n\
\n\
Multi-statement SQL files:\n\
    --sql-file files may contain several statements separated by ';'.\n\
    All statements except the last are executed as DDL/DML (e.g. CREATE TEMP TABLE).\n\
    The last statement must be a SELECT and its result is the command output.\n\
    Example sql file:\n\
      CREATE TEMP TABLE recent AS SELECT * FROM table WHERE year = 2024;\n\
      SELECT category, COUNT(*) FROM recent GROUP BY category;\n\
\n\
Table naming:\n\
    Without explicit names, inputs become table, table2, table3, ...\n\
    Prefix inputs as NAME=PATH to use readable table names in SQL.\n\
\n\
Explicit format:\n\
    Append |TYPE to any path to force a specific format (e.g. ./data.txt|csv).\n\
    The explicit type takes priority over the file extension.\n\
    For clipboard paths use @clipboard|TYPE (e.g. @clipboard|json).\n\
\n\
JSON extraction modes (--json-mode):\n\
    array (default): each element of a top-level array becomes a row.\n\
    object: each key-value pair of a top-level object becomes a row with 'key' and 'value' columns.\n\
    flatten: recursively flattens nested objects/arrays into a single row using dot-separated column names.\n\
\n\
XML extraction modes (--xml-mode):\n\
    rows (default): detect and extract tabular rows from the XML structure.\n\
    descendants: collect every leaf text element as a row with 'tag' and 'value' columns.\n\
    attributes: collect elements with attributes; each attribute name becomes a column.";

pub(crate) const TABLES_AFTER_LONG_HELP: &str = "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 md\n\
Output:\n\
    Lists the logical SQL table names created from your inputs, in input order.\n\
    Use this command before writing queries when you want to confirm automatic table names.\n\
Workflow:\n\
    Typical first step for unfamiliar files; follow with qf schema or qf inspect.";

pub(crate) const SCHEMA_AFTER_LONG_HELP: &str = "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 md\n\
Output:\n\
    Shows one row per column with the logical table name and inferred SQL-compatible type.\n\
    Use this command to verify names and types before writing filters, joins, or casts.\n\
Workflow:\n\
    Pair with qf query when joins or numeric/date filters depend on type inference.";

pub(crate) const INSPECT_AFTER_LONG_HELP: &str = "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 md\n\
Output:\n\
    Includes a compact summary for each table, sampled rows, and optional extra metrics.\n\
    Use this command when you need fast diagnostics before or after adjusting normalization flags.\n\
Workflow:\n\
    Use this as a rapid profiling step for large or messy datasets.";

pub(crate) const DIFF_AFTER_LONG_HELP: &str = "Examples:\n\
    qf diff --key id ./snapshot_2024.csv ./snapshot_2025.csv\n\
    qf diff --key id --show added --format json ./before.json ./after.json\n\
    qf diff --schema-only ./v1.parquet ./v2.parquet\n\
    qf diff --key order_id --ignore-columns updated_at ./orders.xlsx:January ./orders.csv\n\
    qf diff --key id --show all --side-by-side --format md --output diff.md ./left.csv ./right.csv\n\
Output:\n\
    Emits a tabular diff with a leading _diff column (added, removed, changed, unchanged).\n\
    Exit codes: 0 = no differences, 1 = differences found, >1 = errors.\n\
Workflow:\n\
    Ideal for CI checks, monthly snapshot validation, and audit trails.\n\
AI mode:\n\
    Use qf --ai-mode diff ... to emit a JSON envelope with structured diff data\n\
    and metadata, including has_differences and exit_code.";

pub(crate) const PIVOT_AFTER_LONG_HELP: &str = "Examples:\n\
    # Frequency table: count rows by category\n\
    qf pivot --input ./inventory.csv --query 'PIVOT COUNT(*) FROM table GROUP BY category'\n\n\
    # Crosstab: sum stock by category and active status\n\
    qf pivot --input ./inventory.csv --query 'PIVOT SUM(stock) FOR active FROM table GROUP BY category'\n\n\
    # Count by two dimensions\n\
    qf pivot --input ./sales.csv --query 'PIVOT COUNT(*) FOR product_type FROM table GROUP BY region'\n\n\
    # Average price broken down by region\n\
    qf pivot --input ./sales.csv --query 'PIVOT AVG(price) FOR region FROM table GROUP BY product'\n\n\
    # Load the pivot query from a file\n\
    qf pivot --input ./inventory.csv --query-file ./pivot.sql\n\n\
    # Export pivot to CSV, print the generated SQL for inspection\n\
    qf pivot --input ./inventory.csv \\\n\
      --query 'PIVOT SUM(stock) FOR active FROM table GROUP BY category' \\\n\
      --output ./pivot.csv --format csv --show-sql\n\n\
    # Named input table\n\
    qf pivot --input inv=./inventory.csv \\\n\
      --query 'PIVOT COUNT(*) FROM inv GROUP BY category'\n\n\
    # Export pivot to XLSX (static computed data, not a native Excel PivotTable)\n\
    qf pivot --input ./inventory.csv \\\n\
      --query 'PIVOT SUM(stock) FOR active FROM table GROUP BY category' \\\n\
      --output ./pivot.xlsx\n\
Pivot SQL mini-language:\n\
    Syntax: PIVOT AGG_FN( col | * ) [ FOR cols_col ] FROM table_name GROUP BY rows_col\n\
\n\
    AGG_FN must be one of: COUNT, SUM, AVG, MIN, MAX (case-insensitive).\n\
    Use * only with COUNT; all other functions require a column name.\n\
    FOR cols_col is optional — when omitted a single aggregated column is produced.\n\
    Table and column names may be double-quoted for names with spaces or keywords.\n\
    Queries can be stored in a .sql file and loaded with --query-file.\n\
\n\
    Examples:\n\
      PIVOT COUNT(*) FROM table GROUP BY category\n\
      PIVOT SUM(stock) FOR active FROM table GROUP BY category\n\
      PIVOT AVG(price) FOR region FROM sales GROUP BY product\n\
Note on static output:\n\
    qf pivot always computes the pivot table via a SQL CASE WHEN crosstab query and\n\
    writes the result as static data in the chosen output format.\n\
    For xlsx output this means pre-computed cells, not a native Excel PivotTable.\n\
    Use --show-sql to retrieve the generated SQL for reuse in other tools.\n\
Columns dimension:\n\
    When FOR cols_col is present, a preliminary SELECT DISTINCT query retrieves the unique\n\
    values in that column; each value becomes a separate output column.\n\
    When FOR is omitted, a single aggregated column is produced.\n\
Workflow:\n\
    Use qf pivot for quick static reporting, then qf query for deeper drill-down SQL.\n\
AI mode:\n\
    Use qf --ai-mode pivot ... to emit a JSON envelope with pivot rows and\n\
    metadata, including the generated SQL expression.";

pub(crate) const CONV_AFTER_LONG_HELP: &str = "Examples:\n\
    qf conv --input ./data.csv --output ./data.xlsx\n\
    qf conv --input ./data.xlsx:Sheet1 --output ./data.json\n\
    qf conv --input ./data.csv --format parquet --output ./data.parquet\n\
    qf conv --input ./data.json --format csv\n\
    qf conv --input ./a.csv --input ./b.csv --output ./combined.xlsx\n\
    qf conv --input './data.txt|csv' --output ./data.json\n\
    qf conv --input '@clipboard|csv' --output ./data.xlsx\n\
Output:\n\
    Reads one or more input files and writes them to the specified output format.\n\
    When multiple inputs are provided, their rows are concatenated (UNION ALL).\n\
    Use --format or the output file extension to select the target format.\n\
Supported formats:\n\
    csv, json, jsonl, markdown (alias: md), html, xml, xlsx, feather, parquet, table.\n\
Workflow:\n\
    Quick single-step conversion; for complex transformations use qf query instead.\n\
AI mode:\n\
    Use qf --ai-mode conv ... to emit converted rows in a JSON envelope\n\
    together with row/column metadata.";

pub(crate) const CAPABILITIES_AFTER_LONG_HELP: &str = "Examples:\n\
    qf capabilities\n\
\n\
Output:\n\
    Prints JSON describing command support, formats, AI-mode compatibility,\n\
    and stable error-code families for automation clients.\n\
\n\
Workflow:\n\
    Recommended first call for AI agents before building command invocations.";

pub(crate) const INPUT_DATASET_LONG_HELP: &str = "Input dataset path. Supported formats: .xlsx, .xml, .csv, .json, .jsonl, .md, .markdown, .html, .htm, .feather, .parquet.\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\
Append |TYPE to force an explicit format (e.g. ./data.txt|csv, @clipboard|json). The |TYPE suffix takes priority over the file extension.\n\
For clipboard use @clipboard|TYPE (e.g. @clipboard|csv, @clipboard|json, @clipboard|jsonl, @clipboard|md, @clipboard|html, @clipboard|xml).\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) const QUERY_INPUT_DATASET_LONG_HELP: &str = "Input dataset path. Supported formats: .xlsx, .xml, .csv, .json, .jsonl, .md, .markdown, .html, .htm, .feather, .parquet.\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\
Append |TYPE to force an explicit format (e.g. ./data.txt|csv, @clipboard|json). The |TYPE suffix takes priority over the file extension.\n\
For clipboard use @clipboard|TYPE (e.g. @clipboard|csv, @clipboard|json, @clipboard|jsonl, @clipboard|md, @clipboard|html, @clipboard|xml).\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\
For HTML use PATH:N (or PATH#N), where N is the 1-based table number; without key the first table is used.\n\
    CSV, JSONL, Feather and Parquet do not support sheet/key selection.";

pub(crate) const DEFAULT_SELECTOR_LONG_HELP: &str =
    "Default sheet/tag/key applied to every --input that does not already specify one.";

pub(crate) const TABLES_FORMAT_LONG_HELP: &str =
    "Output format for table discovery results. Supported values: text, json, markdown (alias: md).";

pub(crate) const SCHEMA_FORMAT_LONG_HELP: &str =
    "Output format for schema results. Supported values: text, json, markdown (alias: md).";

pub(crate) const INSPECT_FORMAT_LONG_HELP: &str =
    "Output format for inspection results. Supported values: text, json, markdown (alias: md).";

pub(crate) const CLI_FOOTER: &str = "Daniele Olmisani <daniele.olmisani@gmail.com>\n\
https://crates.io/crates/query-forge";