report

Excel report generator for Go. Accepts any tabular data source, auto-sizes columns, and supports per-report customisation via embedded Starlark scripts.
Requires Go 1.26+
go get github.com/abagile/tokyo3-report
Design
ReportConf ──► Generate(conf, rowReader) ──► .xlsx file
│
├── RowsReader (data source: StructRows or SqlxRows)
├── Script (optional Starlark, controls layout & style)
└── Translator (optional, localises headers and enum values)
Generate either creates a new workbook or opens an existing one, replaces the named sheet with fresh data, applies script-defined layout, then saves. If the target file does not exist it is created from scratch; if it exists but contains other sheets those sheets are left untouched.
Quick start
type Order struct {
Region string
Product string
Amount float64
}
rows := []any{
Order{"North", "Widget A", 1200.00},
Order{"North", "Widget B", 450.00},
Order{"South", "Widget A", 980.00},
}
err := report.Generate(
&report.ReportConf{
OutputPath: "orders.xlsx",
SheetName: "Orders",
},
report.NewStructRows(rows),
)
Produces a sheet with auto-titled headers (Region, Product, Amount) and auto-sized columns.
See the example/ directory for a complete runnable program.
ReportConf
| Field |
Type |
Description |
OutputPath |
string |
Path to the .xlsx file to create or update. |
SheetName |
string |
Name of the worksheet to write. |
Script |
string |
Starlark script for layout customisation (see below). |
Translator |
Translator |
Optional interface for header and value localisation. |
QueryPath |
string |
Informational — callers may store the SQL file path here; the library does not read it. |
RowsReader
RowsReader is the interface that decouples Generate from any particular data source.
type RowsReader interface {
Read() error
Headers() ([]string, error)
Values() ([]any, error)
Next() bool
Err() error
}
Two implementations are provided:
StructRows
Wraps an in-memory []any of structs. Struct field names become column headers.
reader := report.NewStructRows([]any{
MyStruct{...},
MyStruct{...},
})
Pointer fields are dereferenced; nil pointers render as an empty string.
SqlxRows
Streams directly from a live *sqlx.DB query.
reader := report.NewSqlxRows("SELECT region, product, amount FROM orders", db)
NUMERIC database columns are automatically converted to float64.
Translator
Implement Translator to localise header names and enum-coded values.
type Translator interface {
T(key string) string // header: raw field/column name → display name
Label(key string, code any) string // value: lookup key + raw code → display label
}
T is called once per header with the raw field or SQL column name.
Label is called per cell for columns that set lookup in the script (see below).
type MyTranslator struct{}
func (MyTranslator) T(key string) string {
return map[string]string{
"region": "Region",
"product": "Product Name",
"amount": "Amount (USD)",
}[key]
}
func (MyTranslator) Label(key string, code any) string {
if key == "status" {
return map[any]string{1: "Active", 2: "Closed"}[code]
}
return fmt.Sprintf("%v", code)
}
When no Translator is set, headers are produced by flect.Titleize (order_id → Order Id).
Starlark script
The Script field accepts a Starlark program. Starlark is a deterministic, sandboxed Python dialect — no I/O, no imports, no side-effects outside the declared variables below.
The script may define any combination of the four top-level variables. Unrecognised variables are silently ignored.
groupFields
groupFields = ["Region", "Category"]
A list of raw column names. Whenever the value in any listed column changes compared to the previous row, a blank separator row is inserted above the new group. Multiple fields are checked in order.
width
width = {
"A": 14,
"B:D": 22,
}
Explicit column widths. Keys are column letters or letter ranges ("B:D"). Values are numbers (integer or float). Overrides auto-sizing for those columns. All other columns remain auto-sized.
Column widths are clamped to the range [10, 100].
style
style = {
"1": '{"font":{"bold":true}}', # entire row 1 (header)
"A": '{"font":{"color":"FF0000"}}', # entire column A
"A:C": '{"fill":{"type":"pattern","color":["FFFFCC"],"pattern":1}}', # columns A–C
"B2": '{"font":{"italic":true}}', # single cell
"B2:D10": '{"font":{"italic":true}}', # cell range
}
Each key selects a target; each value is the JSON serialisation of an excelize Style. Target type is inferred from the key format:
| Key pattern |
Example |
Applied with |
| Column letter(s) |
"A", "A:C" |
SetColStyle |
| Integer |
"1", "3" |
SetRowStyle |
| Cell reference |
"B2" |
SetCellStyle |
| Cell range |
"B2:D10" |
SetCellStyle |
col
Per-column settings keyed by the raw field/column name (before any Translator.T rename).
col = {
"amount": {
"width": 18,
"style": '{"num_fmt":4}', # #,##0.00
"lookup": "payment_status", # calls Translator.Label("payment_status", value)
},
"status": {
"lookup": "order_status",
},
}
| Sub-key |
Type |
Description |
width |
number |
Column width (same semantics as width dict above). |
style |
string |
JSON style applied to the entire column. |
lookup |
string |
Passes each cell value through Translator.Label(key, value). No-op when no Translator is set. |
col style and width entries are merged into the style and width dicts after the script runs, so they can coexist with direct style/width entries.
Style JSON reference
Style values are JSON representations of excelize.Style. Common fields:
{
"font": { "bold": true, "italic": true, "size": 11, "color": "FF0000" },
"fill": { "type": "pattern", "pattern": 1, "color": ["FFFFCC"] },
"alignment": { "horizontal": "center", "wrap_text": true },
"num_fmt": 4 // built-in number format ID (4 = #,##0.00)
}
Common num_fmt IDs: 3 = #,##0, 4 = #,##0.00, 9 = 0%, 10 = 0.00%.
Auto column sizing
Column widths are calculated automatically from content width, where ASCII characters count as 1 unit and CJK/multibyte characters count as 2 units, multiplied by a 1.123 scale factor. The result is clamped to [10, 100]. Script-defined widths always override auto-sizing.
time.Time values receive Excel's built-in short-date format (format ID 14, e.g. 01/15/24) to prevent Excel from auto-converting dates to MMM-YY display format on open.