///| /// A small command-line tool for common spreadsheet operations, built on /// the mbtexcel library. Subcommands: /// /// xlsx create [--sheet NAME] create a new workbook /// xlsx csv [--sheet NAME] import a CSV file into a workbook /// xlsx set set a cell (in place) /// xlsx formula set a cell formula /// xlsx style [flags] apply a cell style /// xlsx merge merge a cell range /// xlsx width set a column (or A:C) width /// xlsx freeze freeze panes above/left of cell /// xlsx filter add an auto-filter over a range /// xlsx add-sheet add a sheet to a workbook /// xlsx chart [opts] add a chart from a data range /// xlsx batch [--dry-run] apply a JSON op script /// xlsx get [--json] print a cell value (or a /// cell/range as JSON) /// xlsx outline print a workbook map as JSON /// xlsx html [--sheet --out ...] render sheets as HTML /// xlsx calc [--raw] evaluate a cell's formula /// xlsx sheets list sheet names /// xlsx rows [--sheet NAME] print a sheet as CSV /// xlsx view [--sheet NAME] print a sheet as an ASCII table /// xlsx validate check OOXML package validity /// /// The subcommand handlers live in `commands.mbt` (and `commands_style.mbt` /// for formula/style/merge); pure output formatting lives in `render.mbt`. async fn main { run_cli() catch { // `--help`/`--version` are handled inside argparse (exit 0). Anything // that reaches here is a real failure. Re-raising it as a `CliError` // lets it escape `main`, which prints the message once and exits // non-zero. Since async 0.21, native errors go to stderr; // wasm errors still go to stdout. err => { // argparse's own errors already start with "error:"; don't double it. let msg = "\{err}" raise CliError( if msg.has_prefix("error:") { msg } else { "error: " + msg }, ) } } } ///| /// A formatted, user-facing CLI error. Its `Show` writes exactly the /// message, so the runtime's uncaught-error print produces one clean line. suberror CliError { CliError(String) } ///| impl Show for CliError with fn output(self, logger) { match self { CliError(message) => logger.write_string(message) } } ///| /// Parses arguments and dispatches to the selected subcommand. Argument /// errors from `parse` propagate to `main`. async fn run_cli() -> Unit { let argv = @env.args() let args = if argv.length() > 0 { argv[1:].to_owned() } else { [] } let command = build_command() let matches = command.parse(argv=args, env=Map([])) match matches.subcommand { Some(("create", m)) => run_create(m) Some(("csv", m)) => run_import_csv(m) Some(("set", m)) => run_set(m) Some(("formula", m)) => run_formula(m) Some(("style", m)) => run_style(m) Some(("merge", m)) => run_merge(m) Some(("width", m)) => run_width(m) Some(("freeze", m)) => run_freeze(m) Some(("filter", m)) => run_filter(m) Some(("add-sheet", m)) => run_add_sheet(m) Some(("chart", m)) => run_chart(m) Some(("batch", m)) => run_batch(m) Some(("get", m)) => run_get(m) Some(("outline", m)) => run_outline(m) Some(("query", m)) => run_query(m) Some(("lint", m)) => run_lint(m) Some(("capabilities", m)) => run_capabilities(m) Some(("html", m)) => run_html(m) Some(("calc", m)) => run_calc(m) Some(("sheets", m)) => run_sheets(m) Some(("rows", m)) => run_rows(m) Some(("view", m)) => run_view(m) Some(("validate", m)) => run_validate(m) _ => println(command.render_help()) } } ///| /// Builds the argparse command tree. fn build_command() -> @argparse.Command { Command( "xlsx", about="Create, inspect, and validate xlsx spreadsheets", subcommand_required=true, subcommands=[ Command( "create", about="Create a new empty workbook", positionals=[PositionArg("output", about="output .xlsx path")], options=[ OptionArg("sheet", long="sheet", about="name of the first sheet", default_values=[ "Sheet1", ]), ], ), Command( "csv", about="Import a CSV file into a new workbook", positionals=[ PositionArg("input", about="input .csv path"), PositionArg("output", about="output .xlsx path"), ], options=[ OptionArg( "sheet", long="sheet", about="name of the sheet to create", default_values=["Sheet1"], ), ], ), Command("set", about="Set a cell value and save the file in place", positionals=[ PositionArg("file"), PositionArg("sheet"), PositionArg("cell"), PositionArg("value"), ]), Command( "formula", about="Set a cell formula (leading '=' optional) and save in place", positionals=[ PositionArg("file"), PositionArg("sheet"), PositionArg("cell"), PositionArg("formula", about="e.g. =SUM(B2:B10)"), ], ), Command( "style", about="Apply a style to a cell or range and save in place", positionals=[ PositionArg("file"), PositionArg("sheet"), PositionArg("range", about="a cell (A1) or range (A1:B2)"), ], flags=[ FlagArg("bold", long="bold", about="bold font"), FlagArg("italic", long="italic", about="italic font"), ], options=[ OptionArg( "number-format", long="number-format", about="number format code, e.g. \"#,##0.00\" or \"0%\"", ), OptionArg( "fill", long="fill", about="solid fill color as hex RGB, e.g. FFFF00", ), OptionArg( "font-color", long="font-color", about="font color as hex RGB, e.g. FFFFFF (white)", ), OptionArg( "align", long="align", about="horizontal alignment: left, center, or right", ), ], ), Command( "merge", about="Merge a cell range (e.g. A1:B2) and save in place", positionals=[ PositionArg("file"), PositionArg("sheet"), PositionArg("range", about="range to merge, e.g. A1:B2"), ], ), Command( "width", about="Set a column's (or a column range's) width and save in place", positionals=[ PositionArg("file"), PositionArg("sheet"), PositionArg("column", about="a column (A) or range (A:C)"), PositionArg("width", about="width in character units, e.g. 18"), ], ), Command( "freeze", about="Freeze panes above and left of a cell (e.g. A2 freezes row 1)", positionals=[ PositionArg("file"), PositionArg("sheet"), PositionArg("cell", about="first scrollable cell, e.g. A2"), ], ), Command( "filter", about="Add an auto-filter over a range and save in place", positionals=[ PositionArg("file"), PositionArg("sheet"), PositionArg("range", about="range to filter, e.g. A1:C10"), ], ), Command( "add-sheet", about="Add a new empty sheet to an existing workbook", positionals=[ PositionArg("file"), PositionArg("name", about="name of the new sheet"), ], ), Command( "chart", about="Add a chart (col/bar/line/pie/…) from a data range and save in place", positionals=[ PositionArg("file"), PositionArg("sheet"), PositionArg("anchor", about="top-left cell of the chart, e.g. E2"), ], options=[ OptionArg( "type", long="type", about="chart type: col (default), bar, line, pie, area, scatter, doughnut, radar", ), OptionArg( "categories", long="categories", about="category-label range, e.g. A2:A6", ), OptionArg( "values", long="values", about="series value range, e.g. B2:B6", ), OptionArg( "name", long="name", about="series legend label (a literal; for a cell use a qualified ref like Sheet1!B1)", ), OptionArg("title", long="title", about="chart title"), ], ), Command( "batch", about="Apply an xlsx.batch/2 JSON op script (v1 retained) and save in place", positionals=[ PositionArg("file"), PositionArg( "script", about="path of the op script (see docs/agent-json-schemas.md)", ), ], flags=[ FlagArg( "dry-run", long="dry-run", about="parse and apply in memory but do not write the file", ), ], ), Command( "get", about="Print a single cell value (or a cell/range as JSON)", positionals=[ PositionArg("file"), PositionArg("sheet"), PositionArg( "cell", about="a cell (A1) or, with --json, a range (A1:B2)", ), ], flags=[ FlagArg( "json", long="json", about="print the cell or range as a structured JSON payload", ), ], ), Command( "capabilities", about="Print the batch op catalog this build supports (JSON)", ), Command("outline", about="Print a workbook structure map as JSON", positionals=[ PositionArg("file"), ]), Command( "query", about="Find cells matching a content selector, as JSON", positionals=[ PositionArg("file"), PositionArg("sheet"), PositionArg( "selector", about="e.g. cell[type=formula], cell[value>100], cell[formula~=SUM]", ), ], options=[ OptionArg( "range", long="range", about="limit the scan to this range (default: the used range)", ), ], ), Command( "lint", about="Report formula cells that evaluate to an error, as JSON", positionals=[PositionArg("file")], options=[ OptionArg( "sheet", long="sheet", about="lint only this sheet (default: all sheets)", ), ], ), Command( "html", about="Render sheets as a self-contained HTML document", positionals=[PositionArg("file")], options=[ OptionArg( "sheet", long="sheet", about="render only this sheet (default: all sheets)", ), OptionArg( "out", long="out", about="write the document here instead of stdout", ), OptionArg( "max-rows", long="max-rows", about="rows to render per sheet (default 1000)", ), OptionArg( "max-cols", long="max-cols", about="columns to render per sheet (default 256)", ), ], flags=[ FlagArg( "no-images", long="no-images", about="skip embedding images as data: URIs", ), FlagArg( "calc", long="calc", about="evaluate formulas that have no cached result", ), ], ), Command( "calc", about="Evaluate a cell's formula and print the computed value", positionals=[ PositionArg("file"), PositionArg("sheet"), PositionArg("cell"), ], flags=[ FlagArg( "raw", long="raw", about="print the unformatted computed value", ), FlagArg( "json", long="json", about="print the typed computed value as a JSON payload", ), ], ), Command("sheets", about="List the sheet names in a workbook", positionals=[ PositionArg("file"), ]), Command( "rows", about="Print a sheet as CSV", positionals=[PositionArg("file")], options=[ OptionArg( "sheet", long="sheet", about="sheet to export (default: first sheet)", ), ], ), Command( "view", about="Print a sheet as an ASCII table", positionals=[PositionArg("file")], options=[ OptionArg( "sheet", long="sheet", about="sheet to view (default: first sheet)", ), ], ), Command( "validate", about="Report OOXML package structure problems (empty = valid)", positionals=[PositionArg("file")], ), ], ) }