Skip to main content

Data Packs

A data pack is a self-describing file holding a list of values (first names, last names, colors, anything) that TDC picks up automatically and exposes at a short dotted address. You add new data without touching code — just drop a file into a data folder.

The example outputs below are illustrative: exact values can shift between core versions, but the shape and the proportions hold.

Three independent axes, and one address reaching into all of them.
  • Ainternational data, the same in every locale
  • Bthe language axis: how names and words are spelled
  • Cthe country axis: what's specific to a single country
  • Done address — which bucket answers it depends on the run, not on the address itself

The rules

  • One file = one homogeneous list = one address. Male and female first names are different lists, each in its own file.
  • Data and declarative generators only — no executable code. A pack is either "take a value from a list" or a generator written in TDC's own DSL (see Writing your own). Nothing arbitrary runs, which is what keeps the cross-language guarantee intact and makes third-party packs safe to download.
  • Plain UTF-8, one value per line. The extension doesn't matter (.txt, .csv, or none at all).

How the address is formed

There are two ways, and they work together.

From the folder structure (default)

The address is the file's path relative to the data folder, without the extension:

data/packs/en/person/male/firstName.txt → en.person.male.firstName
data/packs/es/person/lastName.txt → es.person.lastName

Folder names become the dotted segments. Nothing to declare — the location is the address. Use this for anything that fits a tidy folder tree; it's the whole story for most packs.

From a header (override)

If a file sits off on its own, or needs an address that doesn't match its folder, put a header at the top, fenced by --- lines:

---
description: Color names
address: common.color.name
---
chrome
plasma-blue
void-black

TDC then uses address: instead of the path. The first segment must still be a locale code or common. Use this when the address and the on-disk layout can't be the same — a single shared file, a vendored third-party list, a generated pack.

Header fields

All header fields are optional. The ones that build or redirect the address are covered below; the fields that change how the body is read (weighted lists, external files, generators) get their own worked examples in Writing your own.

FieldMeaningWhere covered
descriptionA human-readable description — "what this is"below
addressAn explicit address, overriding the one computed from the pathbelow
localeWhich language the pack speaks (en, es, fr…)below
filePoint at an external data file instead of an inline bodybelow
columnWith file: take a named/numbered column from a CSVbelow
delimiterThe column/value separator (default ,)below
weightMake the pack weighted: the frequency columnWriting your own
weightedtrue — the body is value,weight linesWriting your own
generatortdc — the body is a <gen>, not a listWriting your own
injectA custom interpolation marker for a generatorWriting your own

description — metadata

Free text describing the pack. It has no effect on the output — it's there for humans, and for the editor autocomplete that will read it later (see Not yet built). Keep it short: "US city names", "HTTP status codes".

address: — override the computed path

address: replaces the path-derived address. The file below sits outside any matching folder tree, yet still resolves at common.color.name:

---
address: common.color.name
---
chrome
plasma-blue
void-black
<gen type="template" value="common.color.name"/>
./run colors.tdc (4 rows)
plasma-blue
void-black
chrome
plasma-blue

Reach for this when a file can't sit where its address says it should: a shared list, or a pack somebody else laid out.

locale: — which language the pack speaks

locale: tags the pack's language so locale-aware resolution can find it. The same logical path resolves to different data per locale: person.lastName gives English surnames under the default en, and es.person.lastName gives Spanish ones.

<gen type="template" value="person.lastName"/>
./run last-en.tdc (4 rows)
Smith
Johnson
Williams
Brown
<gen type="template" value="es.person.lastName"/>
./run last-es.tdc (4 rows)
Garcia
Fernandez
Rodriguez
Lopez

Set it when a pack's data is language-specific and its address doesn't already carry a locale segment. See the template generator for how one path maps across locales.

locale: also supplies the segment the path is missing. A file dropped straight into your own folder — no locale tree above it — derives the address gadget, which starts with no locale and so belongs nowhere. Its own header settles that:

---
description: my own list
locale: en
---
Blender
Grinder
<gen type="template" value="gadget"/>

The pack is registered at en.gadget, and value="gadget" under en finds it. So a file lands at its address by one of three routes: address: if the header names one, otherwise the path, and locale: fills in the locale when the path alone leaves the address homeless. All three work identically in every implementation.

A file that ends up at none of them — a header, but no address:, no locale:, and a path starting with no locale, country or common — is not addressable and is left out. The CLI says so at load time with a TDC171 warning rather than letting it vanish.

An external file as the body — file:, column:, delimiter:

Instead of an inline body, a header can point at an existing file — useful for large lists, or for reusing a CSV you already have. file: is the path (relative to the pack file), column: picks a column by name or number, and delimiter: sets the separator (a character, or one of the aliases tab, semicolon, pipe).

---
description: US city names
file: ../../sources/us/cities.csv
column: name
delimiter: ,
---
<gen type="template" value="usa.city.name"/>
./run cities.tdc (4 rows)
Springfield
Riverside
Franklin
Clinton

The pack has no inline values — the body is the named column of that CSV. The same three fields can also back a weighted list; that variant is covered in Writing your own.

Using an address

You call an address the way you call any template generator. Pull the parts you want into named sequences and assemble them in the .tdc:

<tdc>
<env count="4" seed="demo">
<sequence name="First"><gen type="template" value="person.male.firstName"/></sequence>
<sequence name="Last"><gen type="template" value="person.lastName"/></sequence>
</env>
<block><line><data>${{First}} ${{Last}}</data></line></block>
</tdc>
./run people.tdc (4 rows)
Michael Johnson
Robert Brown
James Smith
John Williams

The address person.lastName is just the file en/person/lastName.txt in the data folder, and calling a template generator on it hands back values from that list.

Compose the record shape yourself

The pack gives you the parts; the shape of the record is yours to build in the .tdc. An English "First Last" and a Spanish "First Last1 Last2" are just two arrangements of the same kind of pack:

<tdc>
<env count="4" seed="es">
<sequence name="First"><gen type="template" value="es.person.male.firstName"/></sequence>
<sequence name="Last1"><gen type="template" value="es.person.lastName"/></sequence>
<sequence name="Last2"><gen type="template" value="es.person.lastName"/></sequence>
</env>
<block><line><data>${{First}} ${{Last1}} ${{Last2}}</data></line></block>
</tdc>
./run es-people.tdc (4 rows)
Anselmo León Muñoz
Simón Redondo Casas
Félix Muñiz Ramos
Isaías Zurita Rendón

The last row draws the same surname twice (Rodriguez Rodriguez): Last1 and Last2 are two independent draws from one list, and nothing stops them from colliding. When two fields in a row have to differ, wrap them in <distinct> — the mechanics are covered in Writing your own.

More than lists

A pack isn't limited to a flat list. It can also be:

  • a weighted list — each value's real-world frequency, laid out exactly, with the same Hamilton method as percent;
  • a small generator written in the DSL — a regex pattern, a name assembled from neighboring lists, or a <mix> that splits by exact percentage.

By convention, generator files use the .tdc extension and plain data stays .txt, so the two are easy to tell apart. Both kinds are safe to ship and download — a generator is a parsed, sandboxed DSL, never arbitrary code — and both are covered end to end in Writing your own.

Where packs live

  • The built-in set ships in the repo under data/packs/ and is scanned automatically on startup.
  • Your own folders are added with the CLI flag --data-path <folder> (repeatable) or the library's dataPaths. See Installing packs for the tdcv2 init / tdcv2 pack flow.

Errors

Problems are caught at load time, before any generation runs:

  • Two files claiming the same addressTDC170, naming both files. Rename or move one.
  • A pack file that lands nowhere — a header, but nothing that gives it a locale, country or common first segment → TDC171, a warning naming the file. Add address: or locale:, or move it under a locale folder.
  • A typo in a path in the config (value="person.male.firstNam") → TDC071, unknown template path.
./run typo.tdc
TDC071: unknown template path 'person.male.firstNam'
did you mean 'person.male.firstName'?

Hidden files (anything starting with .) and README / LICENSE / CHANGELOG are ignored by the scanner.

Not yet built

  • Address autocomplete in the editor, driven by the description: headers — next up.
  • A batch manifest for a whole folder (license, author, version) — later.

See also