A simple spreadsheet builder tool.
This template language is designed to generate XLSX spreadsheets effectively. The underlying library is rust_xlsxwriter.
The template has two main sections:
- Format definitions
- Sheet & content definitions
You can define reusable cell formats:
:header {
border("thin"),
border_bottom_color("#000000"),
background_color("#eeeeee")
}
Format identifiers:
- Must start with
: - Can contain: a-z, A-Z, 0-9, _
Supported format properties:
bolditalicunderlinestrikethroughsupersubnum("")align("left")// left, right, center, verticalcenter, top, bottomindent(1)font_name("")font_size(12)color("#")background_color("#")border("thin")// medium, dashed, dotted, thick, double, hair, etc.border_top(...)border_bottom(...)border_left(...)border_right(...)border_color("#")border_top_color(...)border_bottom_color(...)border_left_color(...)border_right_color(...)
Note: Dates are stored as numbers in Excel → use num() formatting for display.
sheet("Data")
row(0, pixels(75))
col(0, 0, pixels(186))
sheet()→ starts a new worksheetrow()→ sets row heightcol()→ sets column width
Example:
[
img("images/alc-logo.png", :border, embed),
str($report_data.title, :maintitle, colspan(8)),
str($report_data.username, :right, colspan(2))
]
Supported cell types:
str()→ stringnum()→ numberdate()→ ISO → Excel dateimg()→ image
Image modes:
embed→ fits inside cellinsert→ may overflow cells
Merging:
colspan(n)rowspan(n)
You can iterate over arrays:
for $prize in $prize_levels {
[
str($prize.prize_level_no),
str($prize.description),
num($prize.amount)
]
}
Generate multiple cells within a single row:
[ str("Start"), for $val in $arr { str($val) }, str("End") ]
Expands into:
Start | val1 | val2 | val3 | End
Multiple loops in one row:
[
for $val in $arr { str($val) },
for $other in $otherArr { str($other) }
]
- Each loop expands independently
- Final row concatenates all generated cells
Notes:
- Works only at row level
- Ideal for dynamic column generation
- Fully compatible with other expressions
Designed for dynamic column layouts (especially XLSX).
Input format:
$headers = [
["Name", 2],
["Score", 3],
["Notes", 1]
]
String→ labelusize→ number of columns to span
Usage:
header($headers, :gray)
XLSX behavior:
- Cells are merged horizontally
- Format applied to merged region
CSV behavior:
Name,,Score,,,Notes
- No merge support → padded with empty cells
- Keeps column count consistent
Inline usage:
[ str("hello"), header($headers, :gray) ]
if $show_header {
[
str("Header1"),
str("Header2")
]
}
Optional else:
if $cond {
[ ... ]
} else {
[ ... ]
}
Else is optional.
Supported operators:
== != < > <= >=
Supported types:
- string
- integer
- float
Examples:
if $myinteger == 3
if $myinteger < 5
if $myfloat == 5.0
if $mystring == "abc"
if $mystring < $mystring2 // lexicographical
Important:
- Type mismatch → evaluates to false
anchor(@top)
move(@top, 0, 3)
move(0, 3)
cr
anchor()→ save positionmove()→ jump relative or absolutecr→ move to beginning of row
autofit
- Attempts automatic column sizing
- Not always reliable → prefer explicit widths
CSV:
-
No merge support
-
No styling
-
Each row must have consistent number of columns
-
Horizontal expansion (loops, header) helps maintain this dynamically
:header {
bold,
background_color("#cccccc"),
align("center")
}
:gray {
background_color("#eeeeee")
}
sheet("Report")
row(0, pixels(30))
$headers = [
["Name", 2],
["Score", 3],
["Notes", 1]
]
[ header($headers, :gray) ]
for $student in $students {
[
str($student.first_name),
str($student.last_name),
for $score in $student.scores { num($score) },
str($student.note)
]
}
if $show_summary {
[
str("Average", :header),
"",
for $col_avg in $averages { num($col_avg) },
""
]
}
- Dynamic headers (
header) - Horizontal loop for scores
- Conditional summary row
- Always define formats for merged/important cells
- Ensure consistent column count per row → avoids broken CSV/XLSX
- Use anchors and move for complex layouts → easier to maintain
- Prefer explicit column widths over autofit → more predictable output
- Use
header()in CSV cautiously → merges are simulated via empty cells - Type consistency in conditions → mismatched types evaluate to false
- Combine loops and if for compact dynamic tables