ββββββ ββ βββββββ ββ ββ ββββββ βββββββ ββ
ββ ββ ββ ββ ββ ββ ββ ββ ββ
ββββββ ββ βββββ βββ ββ βββββ ββ
ββ ββ ββ ββ ββ ββ ββ ββ
ββ βββββββ βββββββ ββ ββ ββββββ βββββββ βββββββ
pLaInTeXt β eXcEl
Convert CSV files into styled Excel workbooks. Keep your data in plain-text CSVs, define styling in YAML, and generate .xlsx files on demand.
# 1. Create venv and install dependencies (inside this repo)
python3 -m venv venv
./venv/bin/pip install -r requirements.txt
# 2. Add to your .zshrc (adjust path to where you cloned this repo)
export PLEXCEL_DIR="/path/to/sheets-styling"
source "$PLEXCEL_DIR/plexcel-functions.zsh"
# 3. Convert CSVs to Excel
csv-to-xlsx output.xlsx workbook.yamlOr run the Python script directly:
./venv/bin/python csv_to_xlsx.py output.xlsx workbook.yamlβββββββββββββββ ββββββββββββββββ ββββββββββββββ
β CSV files β βββΊ β workbook.yamlβ βββΊ β output.xlsxβ
β (your data) β β (layout/styleβ β (styled!) β
βββββββββββββββ ββββββββββββββββ ββββββββββββββ
β
ββββββββ΄ββββββββ
β styles.yaml β
β (colors/fontsβ
ββββββββββββββββ
You maintain three things:
- CSV files β your raw data (edit in any text editor or spreadsheet)
- workbook.yaml β which CSVs become sheets, and how they're configured
- styles.yaml β reusable named styles (colors, fonts, borders)
csv-to-xlsx output.xlsx workbook.yamlcsv-to-xlsx output.xlsx sales.csv:Sales team.csv:Teamplexcel-init my-config.yaml # Create a starter workbook.yaml
plexcel-styles # Edit your global styles.yamlThis is where you define your sheets. Each entry maps a CSV to a sheet with optional styling:
sheets:
- csv: sales.csv # Path to CSV file
name: Sales Report # Sheet tab name in Excel
freeze_pane: A2 # Lock rows/columns for scrolling
auto_filter: true # Add filter dropdowns to headers
column_widths: {A: 25, B: 15, C: 12}
row_heights: {"1": 30}
wrap_columns: [E, F] # These columns get text wrapping
merged_cells: [A1:D1] # Merge cell ranges
cell_styles: # Apply named styles to ranges
A1:D1: header
dropdowns: # Data validation lists
- range: B2:B100
values: North,South,East,West
formulas: # Computed cells
- column: D
start_row: 2
formula: "=B{row}*C{row}"
number_formats: # Excel number formatting
- column: C
start_row: 2
format: "$#,##0.00"
conditional_formatting: # Color cells by value
- type: cell_is
range: D2:D100
operator: greaterThan
formula: ["10000"]
style: {fill_color: FF92D050}| Option | Example | Description |
|---|---|---|
csv |
data.csv |
CSV file path (required) |
name |
My Sheet |
Sheet tab name |
freeze_pane |
A2 |
Freeze above/left of this cell |
freeze_row |
1 |
Freeze first N rows (alternative) |
auto_filter |
true / row 3 / A1:D1 |
Enable filter dropdowns |
column_widths |
{A: 30, B: 20} |
Set column widths |
row_heights |
{"1": 30, "2": 25} |
Set specific row heights |
wrap_columns |
[E, F] |
Enable text wrap on columns |
merged_cells |
[A1:D1] |
Merge cell ranges |
cell_styles |
{A1:D1: header} |
Apply named styles |
row_styles |
see below | Style rows matching a condition |
dropdowns |
see below | Data validation dropdowns |
formulas |
see below | Computed cell values |
number_formats |
see below | Excel number formats |
conditional_formatting |
see below | Color cells conditionally |
highlight |
see below | Simplified conditional coloring |
Define reusable styles that you reference by name in workbook.yaml:
defaults:
font: Arial 10
row_height: 25.5
date_format: yyyy-mm-dd
align: left center
freeze_row: 1
auto_filter: true
styles:
header:
font: Arial 14 bold white
fill: "#1F3864"
align: center center
border: {bottom: thin}
subheader:
font: Arial 10 bold white
fill: "#2E5597"
align: center center| Property | Shorthand | Example |
|---|---|---|
font |
[family] [size] [bold] [italic] [color] |
Arial 12 bold white |
fill |
hex color or name | "#1F3864" or red |
align |
[horizontal] [vertical] [wrap] |
center center wrap |
border |
style or {edge: style} |
thin or {bottom: thin} |
white, black, red, green, blue, yellow, orange, purple, gray
thin, medium, thick, double, dotted, dashed, hair
freeze_pane: A2 # Freeze row 1 (header stays visible)
freeze_pane: B3 # Freeze row 1-2 AND column A
freeze_row: 1 # Same as freeze_pane: A2
freeze_row: 3 # Freeze first 3 rowsauto_filter: true # Filter on the full data range
auto_filter: row 3 # Filter on row 3 specifically
auto_filter: A3:K3 # Explicit rangeformulas:
# Fill a column with a formula (use {row} as placeholder)
- column: E
start_row: 2
end_row: 100 # Optional, defaults to last row
formula: "=C{row}+D{row}"
# Single cell formula
- cell: B10
formula: "=SUM(B2:B9)"number_formats:
# By column
- column: C
start_row: 2
format: "$#,##0.00"
# By range
- range: E2:E100
format: "0.0%"Common formats: "$#,##0.00" (currency), "#,##0" (thousands), "0.0%" (percent), yyyy-mm-dd (date)
# Full format (with validation messages)
dropdowns:
- range: B2:B100
values: High,Medium,Low
allow_blank: true
prompt: Choose a priority level
# Dict shorthand
dropdowns:
B: [High, Medium, Low]
G: [Active, Inactive]# Full format β compare cell values
conditional_formatting:
- type: cell_is
range: C2:C100
operator: greaterThan
formula: ["10000"]
style: {fill_color: FF92D050}
- type: cell_is
range: F2:F100
operator: equal
formula: ['"Blocked"']
style: {fill_color: FFFF0000, font_color: FFFFFFFF}
# Formula-based β test any condition
- type: formula
range: A2:F100
formula: ['$F2="Active"']
style: {fill_color: FFDEEBF7}Operators: equal, notEqual, greaterThan, lessThan, greaterThanOrEqual, lessThanOrEqual, between, notBetween
# Legacy shorthand
highlight:
- when: {G: Blocked}
fill: red
font: white# Dict format
cell_styles:
A1:D1: header
A2:D2: subheader
# List format
cell_styles:
- range: A1:D1
style: headerApply a style to entire rows where a column matches a value:
row_styles:
- match: {B: Epic}
style: highlight_rowmerged_cells: [A1:D1, A2:D2]
# or
merge: [A1:D1, A2:D2]Row 1 is auto-merged if only the first cell has content.
CSV values are automatically converted to proper Excel types:
| CSV value | Excel type |
|---|---|
42 |
Number (integer) |
3.14 |
Number (float) |
true / false |
Boolean |
2026-07-01 |
Date |
07/01/2026 |
Date |
| Everything else | Text |
The example/ directory contains a ready-to-run workbook:
example/
βββ workbook.yaml # Defines 2 sheets with full styling
βββ example-sales.csv # Sales data (formulas, dropdowns, conditional formatting)
βββ example-team.csv # Team directory (merged title, bold, bullet points)
Try it:
cd example
csv-to-xlsx output.xlsx workbook.yaml- First row automatically gets the
headerstyle - Use
freeze_pane: A2on every sheet for better scrolling - Use
auto_filter: trueto enable sorting/filtering in Excel - Numbers and dates in CSVs are auto-detected, so no manual is conversion needed
- Column letters: A, B, C, ... Z, AA, AB, ...
- Maintain CSV as source of truth and regenerate
.xlsx