genmark
Genmark is a plain-text genealogy domain-specific language (DSL) that
compiles to GEDCOM 5.5.1. The compilation is important because GEDCOM
is so common in genealogical work, but Genmark on its own forms an
extremely useful plain text file format (.gmd) for recording and
organizing genealogical data. There’s good a chance this is the
project I’m most proud of. It feels like more of an achievement to
invent a new file format, and I’m actually using it for all of my own
genealogical research. As far as I know, it could be the only plain
text format specifically for writing/reading genealogical data
directly.
GEDCOM 5.5.1 is the standard interchange format for genealogy software, but it’s a machine format, not something anyone would want to write by hand. Here’s an example from the Genmark README:
0 @I_mary_smith@ INDI
1 NAME Mary Ellen /Smith/
1 SEX F
1 BIRT
2 DATE MAR 1895
2 PLAC Portland, Oregon
1 DEAT
2 DATE 28 FEB 1978
2 PLAC Portland, Oregon
1 FAMS @F1@
That same data represented in Genmark is:
Mary Ellen Smith [mary_smith]
sex: F
b: 1895-03 @ Portland, Oregon
d: 1978-02-28 @ Portland, Oregon
So Genmark exists basically to put a readable layer on top of that:
you write a person as one block of plain text, and the compiler
produces valid GEDCOM. And really, if you don’t need to use GEDCOM,
Genmark is a great format by itself. The idea is that the .gmd file
is the actual research archive - not an import/export intermediate -
so it can be read, diffed, grepped, and edited directly. The repo
includes configuration files for Emacs, Vim, and VS Code to make
editing there even more convenient.
The Genmark program is written in Go and ships as a single static
binary via go install or prebuilt cross-platform releases (Linux,
macOS Intel/ARM, Windows). The compilation process follows the
following pipeline: a lexer tokenizes the source, a parser builds an
AST, and a compiler walks it to emit GEDCOM, with a first pass to
register every record’s ID before a second pass resolves references -
so forward references (citing a person before their record appears)
will work fine.
I didn’t include absolutely everything available in GEDCOM, but I tried to cover most of what people would need for real genealogical research. Life events - birth, death, christening, burial, immigration, naturalization, residence, census, occupation, military service, education, arbitrary custom events - all take a common description/date/place shape:
occ: Factory Foreman (1921..1945) @ Portland, Oregon
There’s a wide range of options for how you might want to organize
your genealogical data, largely based on how much data you’re storing
on how many people. You could keep it all in one file if you want to,
but Genmark compiles across multiple files and even whole directory
trees, resolving every [id] reference globally. You can organize the
data however makes sense: one file per person, one per branch of the
tree, one per surname or region, etc. A combine subcommand
concatenates multiple .gmd files back into one, preserving comments
and formatting, for when you want a single reviewable document without
producing GEDCOM.
Sharing a tree usually means redacting any living people, so compile --privatize strips anyone not demonstrably (or assumed, by
configuration) dead. Media files can be attached to a person via a
media: field and can live in per-person or per-branch directories
alongside the .gmd files; --copy-media gathers everything actually
referenced in the compiled output into a single bundle beside the
GEDCOM (skipping media orphaned by privatization) so the tree is
portable without manually collecting/reorganizing files.
Beyond compiling, there’s a small set of analysis subcommands aimed at
research itself rather than output: check validates a set of files
for undefined references and conflicting facts without producing a
GEDCOM, and shows every maybe: as a running list of things to
verify; places tallies where events happened, with a --by-region
mode that rolls up to state or country and an --at YEAR mode that
infers each living person’s approximate location at a given point in
time from their most recent dated event; and names tallies given,
middle, and surnames across the dataset, useful for spotting naming
patterns or picking a name that runs in the family.
You can see the code and download the binary here on GitHub. Again, I’m using this for my own work without really needing the GEDCOM compilation at all (so far), so the most important details are in the documentation/specs, not the code (although the program’s other subcommands are also useful). For a very thorough description of the syntax see SYNTAX.md, and for more examples you can look at EXAMPLES.gmd.