Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
oa — Office Automation CLI
A Windows CLI tool that automates Microsoft Office (PowerPoint + Excel) via the COM API. Built in Rust for speed and safety.
Takes a PowerPoint template with linked OLE objects and Excel data, then updates tables, charts, delta indicators, and color coding — producing a fully populated report in seconds.
Features
- Update pipeline — Re-link OLE objects, populate tables, swap delta arrows, apply color coding, update charts
- Batch processing — TOML runfiles for processing dozens of files in one command
- Validation — Cell-by-cell check of PPT values against Excel source data
- ZIP pre-processing — Rewrite OLE/chart paths and chart data directly in PPTX XML (100x faster than COM for links)
- Inspection — Read-only analysis of PPTX shape inventory with per-slide breakdown
Installation
Requires Windows with Microsoft Office (PowerPoint + Excel) installed.
One-liner (no Rust needed)
PowerShell:
powershell -ExecutionPolicy ByPass -c "irm https://raw.githubusercontent.com/albertxli/office-automation/main/install.ps1 | iex"
Downloads oa.exe, installs to %LOCALAPPDATA%\oa, and adds it to your PATH automatically. Restart your terminal after install.
Or download manually from GitHub Releases.
Via crates.io (requires Rust)
Build from source
Quick Start
# Update a presentation with new Excel data
# Batch process from a runfile
# Validate output against Excel
# Inspect a PPTX file
# Per-slide shape breakdown
# Where does a text token appear? (no PowerPoint needed; exit 1 = not found)
Commands
| Command | Description |
|---|---|
oa update |
Run the update pipeline on PPTX files |
oa run |
Execute a TOML runfile for batch processing |
oa check |
Validate PPT values against Excel source, plus shape naming (every delt_/table shape needs an OLE partner) |
oa info |
Inspect a PPTX file (read-only) |
oa find |
Search text inside a PPTX (ZIP-level, no Office needed) |
oa diff |
Compare two PPTX files side by side |
oa config |
Show all config keys and defaults |
oa clean |
Kill zombie Office processes |
See API.md for full command reference with all options and examples.
Runfile Example
= "../data"
= "output/{name}.pptx"
[]
= "templates/template.pptx"
[[]]
= "Australia"
= "t1"
= "tracking_australia.xlsx"
[[]]
= "Japan"
= "t1"
= "tracking_japan.xlsx"
See example_runfile.toml for a complete example.
Pipeline Steps
The update pipeline runs these steps in order:
| Step | Description |
|---|---|
| Links | Re-point OLE links to the new Excel file |
| Tables | Populate PPT table cells from Excel ranges |
| Deltas | Swap delta indicator arrows based on value sign, with optional dead-band thresholds (see below) |
| Coloring | Apply sign-based color coding to _ccst tables |
| Charts | Rebuild everything a linked chart references — values, category labels, series names, scatter/bubble data — the same result as PowerPoint's own refresh, with the link left on manual update; blank cells draw nothing, a real 0 draws a zero bar; series formulas that name a workbook ([book.xlsx]Sheet!Range) are normalised and reported as a warning |
| Replace | Replace literal text tokens (-r [country]=Japan) across slides, masters and layouts; only runs when replacements are given (see below) |
Steps can be selectively run or skipped:
Shape Naming Conventions
The pipeline identifies shapes by name prefix:
| Prefix | Type | Description |
|---|---|---|
ntbl_ |
Normal table | Updates cell text, preserves formatting |
htmp_ |
Heatmap table | Applies 3-color scale from Excel |
trns_ |
Transposed table | Swaps rows/columns from Excel |
delt_ |
Delta indicator | Arrow swapped by value sign |
delt<N>_ |
Delta indicator, set N | Same as delt_, but copies from the tmpl<N>_delta_* templates |
_ccst |
Color-coded | Cells colored positive/negative/neutral |
Multiple delta template sets
Delta arrows are replaced by copying a template shape from slide 1. When a deck needs more than one arrow style (for example arrows with text on most slides and text-free arrows on a few), number the set on both sides:
| Set | Shape prefix | Templates on slide 1 |
|---|---|---|
| 1 | delt_ (or delt1_) |
tmpl_delta_pos, tmpl_delta_neg, tmpl_delta_none |
| 2 | delt2_ |
tmpl2_delta_pos, tmpl2_delta_neg, tmpl2_delta_none |
| N | delt<N>_ |
tmpl<N>_delta_pos, tmpl<N>_delta_neg, tmpl<N>_delta_none |
The OLE name and the _pos/_neg/_none suffix work exactly as for delt_, e.g.
delt2_Rev_DE_pos. If a set's templates are missing, that set is skipped with a
warning and other sets still update. oa info lists every set found and its templates;
oa check validates all sets.
A delta or table shape pairs with the OLE object on the same slide whose name it contains as
a whole word (delt2_globalnet_f ↔ globalnet_f). A typo on either side (globalnet_g) leaves
the shape untouched, so oa update warns about every unpaired delt_/ntbl_/htmp_/trns_
shape (Slide 15 │ delt2_globalnet_f · no OLE object matches on this slide (closest OLE name: globalnet_g) — not updated), oa check fails them in a Pairs row, and oa info lists them.
OLE objects that drive no table or delta are only counted: a standalone linked picture is fine.
Delta thresholds
By default a delta is pos when the cell is > 0, neg when < 0, else none. Net-opinion
cells are built from rounded percentages, so tiny deltas are noise. A dead band turns them into
none: with threshold t, value >= t is pos, value <= -t is neg, anything strictly in
between is none.
Thresholds are config keys. delta.threshold applies to every delta; delta.threshold.<token>
applies to deltas whose paired OLE object name contains <token> as a whole word (_ counts as
a word boundary, so globalnet covers globalnet_pet and globalnet_dig but not
globalnetwork). When several tokens match, the longest wins.
# CLI: one --set per key
# TOML runfile — keys contain dots, so quote them
[]
= 0.02
= 0.05
# Python runfile
=
Values are always decimals, read numerically from Excel: a cell typed as a percentage and
showing 2% is 0.02. With the two thresholds above:
| Cell shows | Read as | globalnet (0.02) |
marketnet (0.05) |
|---|---|---|---|
| -3% | -0.03 | neg | none |
| -2% | -0.02 | neg (boundary is inclusive) | none |
| -5% | -0.05 | neg | neg |
| -1% | -0.01 | none | none |
A text or blank cell under a threshold prints a warning and sets the delta to none. Deltas with
threshold 0 (the default) keep the original sign test unchanged. Run oa check with the same
--set values, otherwise it reports the dead-band deltas as mismatches. -v shows the threshold
and token used per delta (-0.02 → neg · thr 0.02 via globalnet).
Text replacement
Put a literal token such as [country] anywhere in the template and have it replaced after all
other steps have run. Matching is literal and case-sensitive; the find string is whatever you
type, so pick a marker that never occurs naturally in a report ([[country]] is safest, plain
[country] works fine too).
# CLI: -r FIND=VALUE, repeatable (--replace is the long form)
# TOML runfile: a global [replace] table, optionally overridden per job.
# {name} expands to the job name, exactly as in default_output.
[]
= "{name}"
= "Wave 3"
[[]]
= "Japan"
= "t1"
= "rpm_2025_japan_v1.xlsx"
[[]]
= "France"
= "t1"
= "rpm_2025_france_v1.xlsx"
= { = "France (FR)" } # wins over the global entry for this job
Scope: every shape on every slide (including grouped shapes and table cells), every slide master
and every custom layout. Speaker notes are not touched. Replacement uses PowerPoint's own
TextRange.Replace, so the formatting of the replaced text is kept and tokens that PowerPoint
split across formatting runs are still found. A token that matches nothing anywhere in the deck
prints a warning (⚠ token "[wave]" was not found anywhere in the deck), which is usually a typo.
-v lists every hit: Slide 1 │ TextBox 3 [country] → Japan (1). Python runfiles do not
support [replace].
To verify a run, search the output with oa find (pure ZIP, no PowerPoint, milliseconds):
Each hit is listed as Slide 3 │ TextBox 60 Geography: [country], with layouts, masters and
speaker notes labelled Layout Title Slide, Master 1, Notes 3. Text split across formatting
runs is joined per paragraph before matching, so a phrase is found even when PowerPoint stored it
in pieces. Exit codes follow grep: 0 found, 1 not found, 2 error.
Performance
| Scenario | Time |
|---|---|
| Single 68-slide PPTX (155 OLE, 257 charts) | ~6s |
Batch 26 files via oa run |
~2m 36s |
| ZIP pre-relink (411 links) | 0.1s |