CSV and Data Tables in Hugo
I figured out how to write Hugo shortcodes to generate tables from CSV and other formats. Didn’t even occur to me that it was possible — or this easy — so I had to share.
This approach only works as-is for uniform, shallow structures: every row has the same number of fields, and every field translates cleanly to a string. If you have more complex structures, you need more complex templates.
Most Markdown parsers include some way to handle tables. Usually it involves drawing your table with ASCII characters. Something like this, from an older post of mine about elscreen:
I can read it just fine, but I find managing Markdown tables tedious without editor extensions. I want easy tables. I don’t care if they look like a table while I’m editing them. If I can copy and paste something into a shortcode? Even better.
reStructuredText and Asciidoctor both provide table-handling approaches beyond drawing ASCII, though the default rst table is lovely if you do like fiddling with columns. I looked at them for shortcode inspiration — particularly rst’s csv-table and list-table directives.
First up: CSV, “Comma-Separated Values”. I work a fair amount with CSV on the command line. I may want to copy and paste something into a table for a blog post every once in a while.
csv-table shortcode could contain any CSV data. Maybe something from the
Awesome Public Datasets? Nah, I’ll just use my https://plausible.io
visitor count for the last week.
My shortcode receives that data as a string in the
variable. How to turn that string into a table?
transform.Unmarshal a formatted string, and it
gives you back a data structure. CSV text becomes an array of arrays, which we
turn into a table by iterating through everything with
Voila! Instant table!
Not bad, but it could be better.
- that first row provides column names, which works better as table headers than just another row
- I prefer a particular style for numeric columns
- what about a summary caption?
Give me a minute.
Fine-tuning the table with parameters
I’ll add a parameter for the caption. Maybe another parameter indicating whether to expect a header row, since the first row of CSV doesn’t always contain column names.
Now that I know how I want to use the shortcode, it’s time to implement the details.
- if there’s a header row,
afterlets me skip past it when building the data rows
- The regular expression I hand to
findREis a little naive, but it works for today
I still need to fiddle with my styles. This table’s a little wide for these values. Maybe later.
CSV is great, but
transform.Unmarshal supports other formats. What about those?
Digression: data tables
I got a little carried away when I learned how much
do. You could get a data structure from CSV, JSON,
TOML, or YAML!
What about — what about a data table? Mind you, I’m not talking about Hugo
data files or
getJSON. That’s a great idea for
No, I’m talking about something similar to the
csv-table case: arrays of JSON
objects you paste in from somewhere else to add a little information to your
Heck, you don’t even need parameters. You could put caption and header details in the data! Might be a good idea to use a list of desired columns instead of a simple flag. That way we can pick and choose columns without editing the row objects.
Suppose I extracted details for the US and a couple neighbors from COVID19API.
The logic looks similar to
csv-table, with adjustments for data format
These header fields use camel case
names like “TotalRecovered”. Piping them through
title transforms them into distinct
capitalized words: “Total Recovered.” That’s easier for me to read in a
And — sadly, considering that the topic is
lang.NumFmt makes large
numbers more readable.
Wonderful! Wonderful formatting, anyways. The details are pretty sobering. People! Wash your hands and wear a mask!
There’s really only one slight problem with
data-table. I don’t need it.
Not today, anyways.
What I need: list tables
What about that first table I mentioned? You know, the
reference? That is the kind of table I need a shortcode for. Something like a
I tried different approaches with
transform.Unmarshal and mashing YAML, TOML,
or JSON lists into something useful. That got frustratingly brittle. Time to
step back and reevaluate. What’s the simplest structure that still does what I
Maybe something line-oriented?
Every line contains one field. Blank lines separate table rows. No special prefix characters needed, since everything’s already in a shortcode.
I like it. Easy to write, easy to read, and easy to parse with
split. Well — you need to
trim a leading newline because of how
.Inner gets handed off, but that’s the only wrinkle so far.
Perfect. This will keep me going for a while. Time to stop before I get too clever.
Try to keep the original goal in mind when working on a thing. I could try making a universal data table shortcode. I don’t need a universal data table shortcode. Not yet, anyways.
- Make a universal data table shortcode.
Okay not really, but I can see a few specific conveniences I’d like to add eventually:
- improve the numeric value handling to recognize and properly format decimal values, including money.
- format dates and timestamps
- support building a simple table from
- control column widths
- control column alignment
- refactor into partials where I can, so there’s less duplication between
I might steal more ideas from reStructuredText. It’s fun!
Speaking of fun, the dog wants to go outside again.