Skip to content

iand/genster

Repository files navigation

genster

go.dev reference Check Status Test Status

genster generates a static family history website from a GEDCOM or Gramps database. It uses a two-step pipeline: gen writes markdown content files, then build renders them into a complete HTML site ready to serve or rsync to a server.

I created it for myself so it makes many assumptions about my particular style of using GEDCOM, especially around Ancestry exports.

Installation

As of Go 1.19, install the latest genster executable using:

go install github.com/iand/genster@latest

This will download and build a binary in $GOBIN.

Workflow

genster gen   --gedcom family.ged --config mytree.kdl --output content/
genster build --content content/ --pub pub/
npx serve pub/           # or: rsync pub/ user@host:/var/www/html/

Step 1 — gen reads the genealogy data and writes markdown files with YAML front matter into the content directory. These files represent every person, place, source, family, citation, list page, and chart in the tree. Manual content (the research diary, stories, a home page) lives alongside generated files in the same content directory.

Step 2 — build walks the content directory, parses each markdown file, renders the body through goldmark, applies an HTML template based on the layout front-matter field, and writes complete HTML pages to the pub directory. Non-markdown files (images, media) are copied verbatim.

The pub directory is self-contained and can be deployed directly with rsync, scp, or any static hosting service.


Commands

genster gen — generate content from genealogy data

Reads a GEDCOM or Gramps file and writes markdown content files to a directory. Exactly one of --gedcom or --gramps must be supplied.

Flag Short Description
--gedcom <file> -g GEDCOM file to read
--gramps <file> Gramps XML file to read
--gramps-dbname <name> Name of the Gramps database, used to keep IDs stable across exports
--config <file> -c Path to the KDL tree configuration file (required; see Tree configuration file)
--output <dir> -o Directory to write generated content files into
--basepath <path> -b URL path prefix for all links (default /)
--key <id> -k ID of the key individual; sets the anchor person for relation filtering and ancestor charts
--relation <mode> Filter which people get pages: any (default), common (must share a common ancestor with key person), or direct (must be a direct ancestor)
--include-private Include living people and those who died within the last 20 years (normally redacted)
--wikitree Generate WikiTree markup on person pages for copy-and-paste
--inspect <type/id> Print the internal data structure for one object (e.g. person/I123) and exit
--debug Embed debug information as inline HTML comments
--verbose / --veryverbose Increase log verbosity

Place maps

When the MAPTILER_API_KEY environment variable is set and a place has coordinates, gen downloads a static map image and embeds it inline on the place page. Maps are sourced from MapTiler Cloud, which hosts the National Library of Scotland historic map layers as well as OpenStreetMap raster tiles.

The map layer and zoom level are chosen automatically based on place type:

Coverage Layer Used for
UK — smaller places Ordnance Survey six-inch to the mile, 1888–1913 Street, building, address, hamlet, village, parish, burial ground
UK — larger places Ordnance Survey one-inch Hills edition, 1885–1903 Town, city, county, country
Greater London Ordnance Survey five-foot to the mile, 1893–1896 Any place in the London hierarchy
Ireland Bartholomew quarter-inch to the mile, 1940 Republic of Ireland and Northern Ireland
Elsewhere OpenStreetMap Places outside UK and Ireland coverage

Each place map includes a downward-pointing arrow marking the place's coordinates and a figcaption with a link to the interactive map viewer (NLS Geo/Explore for historic layers, OpenStreetMap for the OSM fallback).

Tile cache — individual map tiles are cached in $XDG_CACHE_HOME/genster/maptiles/ (defaulting to ~/.cache/genster/maptiles/ when XDG_CACHE_HOME is not set), organised as {layer}/{z}/{x}/{y}.jpg. Cached tiles are reused on subsequent runs.

Image cache — stitched 800×600 JPEG map images are cached in $XDG_CACHE_HOME/genster/maps/ as place-map-{id}.jpg. If the cached image exists it is copied directly to the output media directory without re-downloading any tiles. Delete a cached image to force it to be regenerated.

genster build — render content to HTML

Walks a content directory and renders every markdown file into a complete HTML page.

Flag Short Description
--content <dir> -c Content directory to read (required)
--pub <dir> -p Output directory for rendered HTML (required)
--assets <dir> -a Directory of static assets (CSS, JS) to copy into pub; embedded defaults used when not set
--base-url <url> Scheme and host for absolute URLs in sitemap.xml (e.g. https://example.com); sitemap is omitted when not set
--include-drafts Publish pages marked draft: true
--verbose / --veryverbose Increase log verbosity

genster chart — generate a standalone family tree chart

Produces an SVG family tree chart directly from a GEDCOM or Gramps file without generating a full site. Chart types: descendant, ancestor, butterfly, fan, focus.

genster report — produce a text report

Outputs a plain-text descendant or familyline report to stdout.

genster annotate — annotate diary markdown files

Walks a directory of hand-authored markdown files and replaces bare footnote references ([^label]) with fully-rendered citation links drawn from the genealogy database. Pass --undo to strip the generated citations and restore original syntax.


Tree configuration file

All commands that load genealogy data accept --config/-c pointing to a KDL 1.0 file. The file has three top-level nodes: tree, surname-groups, and annotations.

tree — tree identity and description

tree id="weston" {
    name "The Weston and Pryor Family Tree"
    description r#"
        The Westons were a farming family from rural Shropshire, England, working
        the same land from the mid-17th century until the agricultural depression
        of the 1870s drove the younger sons into the Midlands coalfields.

        The Pryors came from South Wales and moved to Birmingham in the 1890s,
        where Thomas Pryor met and married Alice Weston in 1903.
        "#
}
Child node Description
id Short identifier used in URL paths (e.g. trees/weston/)
name Display name shown on the tree overview page
description Multi-paragraph description rendered on the tree overview page. Use a raw string (r#"..."#) for multi-line text. Blank lines produce paragraph breaks.

id may also be written as a property on the tree node: tree id="weston" { ... }.

surname-groups — variant surname groupings

Groups spelling variants under a single canonical surname for surname list pages and relationship display.

surname-groups {
    Weston "Wheston" "Westen"
    Pryor "Prior" "Pryer" "Priar"
    Griffiths "Griffith" "Gryffith"
    Hughes "Hughs" "Hewes"
}

Each child node names the canonical surname; its arguments are the variants that should be merged into that group.

annotations — corrections and additions

Overrides and supplements data from the source GEDCOM or Gramps file. Organised into people, places, and sources sections.

annotations {
    people {
        person id="A3KMNP2XWQR8T" {
            // Thomas Weston (1847-1921)
            nickname "Tom"
            olb "Agricultural labourer turned coal miner from Shropshire."
            wikitreeid "Weston-4521"
        }
        person id="B7YQZR4VNDX2L" {
            // Alice Pryor (1882-1954)
            preferredgivenname "Alice"
            preferredfamilyname "Weston"
        }
        person id="C9WLFT6HJMS3K" {
            // Living relative
            redacted true
        }
        person id="D2PXNK8GRVC5M" {
            // William Griffiths (1801-1879)
            tags "Direct Ancestor" "Shropshire"
        }
    }

    places {
        place id="E5QRTB9YNWJ7H" {
            // Shifnal, Shropshire — missing coordinates
            latlong "52.669, -2.368"
        }
        place id="F1MVZK3DCXP4G" {
            // Historical variant name
            name "Brewood, Staffordshire, England"
        }
    }

    sources {
        source id="G8HNWQ2YBXR6T" { iscivilregistration }
        source id="H4KCJP7FMVZ3L" { iscivilregistration }
        source id="J6TRWB5SDXN9Q" { iscensus }
        source id="K2LQMF8PVYC4H" { iscensus }
        source id="M9ZXDN3JWKR7B" {
            title "Shropshire Parish Records 1600–1900"
            repositoryname "Shropshire Archives"
        }
    }
}

Person annotation fields

All fields use replace semantics (overwrite the loaded value) except tags, which appends.

Field Type Description
nickname string Preferred nickname
olb string One-line biography
epithet string Distinguishing epithet (e.g. "the Elder")
preferredfullname string Override full display name
preferredgivenname string Override given name
preferredfamiliarname string Override informal given name
preferredfamiliarfullname string Override informal full name
preferredfamilyname string Override family name
preferredsortname string Override sort key
preferreduniquename string Override unique display name
wikitreeid string WikiTree profile ID
possiblyalive bool Mark as possibly still living
unmarried bool Mark as known to have never married
childless bool Mark as known to have had no children
illegitimate bool Mark as born outside marriage
redacted bool Suppress this person from the published site
featured bool Highlight this person
tags string or list Append one or more tags

Place annotation fields

Field Type Description
name string Override place name
latlong string Set coordinates as "lat, long" (e.g. "52.669, -2.368")
tags string or list Append one or more tags

Source annotation fields

Field Type Description
title string Override source title
searchlink string URL for searching this source online
repositoryname string Override repository name
repositorylink string URL for the repository
iscivilregistration bool Mark as a civil registration record
iscensus bool Mark as a census record
isunreliable bool Mark as an unreliable source
tags string or list Append one or more tags

Content directory layout

The content directory holds both generated and hand-authored files. gen writes into trees/<tree-id>/; manual content sits at the top level alongside it.

content/
├── index.md                          # site home page  (layout: home)
├── images/                           # generic feature images — see below
├── diary/                            # hand-authored research diary
│   ├── index.md                      # diary home      (layout: diary)
│   └── 2024/
│       ├── index.md                  # year index      (layout: diary)
│       └── 2024-03-15/
│           └── index.md              # daily entry     (layout: single)
├── stories/                          # hand-authored narrative articles
│   ├── index.md
│   └── my-story/
│       └── index.md                  #                 (layout: single)
└── trees/
    └── <tree-id>/                    # one subtree per --id value
        ├── index.md                  # tree overview   (layout: treeoverview)
        ├── person/<id>/index.md      #                 (layout: person)
        ├── place/<id>/index.md       #                 (layout: place)
        ├── source/<id>/index.md      #                 (layout: source)
        ├── citation/<id>/index.md    #                 (layout: citation)
        ├── family/<id>/index.md      #                 (layout: family)
        ├── chart/<id>/index.md       #                 (layout: chartancestors)
        └── list/
            ├── people/               #                 (layout: listpeople)
            ├── surnames/             #                 (layout: listsurnames)
            ├── places/               #                 (layout: listplaces)
            ├── sources/              #                 (layout: listsources)
            ├── todo/                 #                 (layout: listtodo)
            ├── changes/              #                 (layout: listchanges)
            ├── anomalies/            #                 (layout: listanomalies)
            ├── inferences/           #                 (layout: listinferences)
            ├── families/             #                 (layout: listfamilies)
            └── familylines/          #                 (layout: listfamilylines)

The build command also generates /tags/ pages automatically from front-matter tags — do not create these manually.

Generic feature images

build looks for generic images in content/images/. These are never embedded in the binary, so you can supply your own. The expected filename conventions are:

Filename pattern Used for
person-{gender}-{era}-{trade}-{maturity}.webp Most specific person silhouette
person-{gender}-{era}-{trade}.webp Person by gender, era, and trade
person-{gender}-{era}-{maturity}.webp Person by gender, era, and maturity
person-{gender}-{era}.webp Person by gender and era
person-{gender}.webp Person by gender only
place-{placetype}.webp Place by type (e.g. place-village.webp, place-parish.webp)
place-building-{kind}.webp Building by kind (e.g. place-building-church.webp)
place-building.webp Building fallback
place.webp Place final fallback
section-diary.webp Diary section
section-stories.webp Stories section
section-trees.webp Trees list page
section-search.webp Search page
category-people.webp People list
category-surnames.webp Surnames list
category-place.webp Places list
category-sources.webp Sources and citations
category-todo.webp To-do list
default-oak.webp Final fallback for all other pages

For person pages, build tries the five person patterns from most to least specific and uses the first file it finds. If nothing matches, no image is rendered.

Person-specific photos (actual photographs from Gramps) are set via the image: front-matter field by gen and take priority over all generic images.


Front-matter fields

Every content file begins with a YAML front-matter block delimited by ---. gen sets these automatically on generated pages; hand-authored files may set any field explicitly.

Core fields

Field Type Description
title string Page title, shown in <h1> and <title>
layout string Template to use (see Layouts below)
id string Stable identifier for the entity this page represents
summary string Short description used in meta tags and section listings
draft bool When true, excluded from build output unless --include-drafts is set
lastmod string Last-modified date YYYY-MM-DD, shown in the page footer
aliases list Additional URL paths that redirect to this page
tags list Tag names; build generates a /tags/<slug>/ page for each
image string URL of a person-specific photo; takes priority over all generic silhouettes

Tree navigation

Field Type Description
basepath string Base URL of the tree this page belongs to (e.g. /trees/mytree/); drives the per-tree nav bar

Pagination (list pages only)

Field Type Description
first string Stem of the first page in the sequence
last string Stem of the last page
next string Stem of the next page
prev string Stem of the previous page

Person-specific fields

Field Type Values Description
category string person Marks this as a person page
gender string male, female, unknown Used to select silhouette
era string 1600s 1700s 1800s 1900s modern Derived from birth/death year
maturity string child young mature old Derived from age at death
trade string labourer miner nautical crafts clerical commercial military service Derived from occupation group
ancestor bool true if this person is a direct ancestor of the key person
grampsid string Gramps handle
slug string Short alias for diary links (e.g. john-smith/r/john-smith)
diarylinks list of {title, link} Research diary entries mentioning this person
links list of {title, link} External links (Ancestry, FindMyPast, etc.)
descendants list of {name, link, detail} Ancestor path entries in the sidebar
wikitreeformat string WikiTree markup (set when --wikitree is used)

Place-specific fields

Field Type Description
category string place
placetype string Type of place: city, town, village, hamlet, parish, county, country, building, street, address, etc.
buildingkind string Kind of building when placetype is building: church, workhouse, farm, hospital, etc.

Source/citation fields

Field Type Description
category string source or citation

Story/diary fields (hand-authored)

Field Type Description
author string Author name
started string Date started
updated string Date last updated
status string Completion status (e.g. draft, complete)

Sitemap control

Field Type Description
sitemap map Set {disable: "1"} to suppress this page from sitemap.xml

Templating system

The build command uses Go's html/template package. All templates live in genster/build/templates/ and are embedded into the binary at compile time.

Template data

Every template receives a PageData value as its dot (.):

type PageData struct {
    FrontMatter        // all front-matter fields promoted onto dot
    Body  template.HTML // rendered HTML from the markdown body
    Tree  TreeData      // tree-level metadata
}

type TreeData struct {
    Title    string // title of the tree's index page
    BasePath string // base URL path (e.g. /trees/mytree/)
}

Because FrontMatter is embedded, all front-matter fields are accessible directly — for example {{.Title}}, {{.Category}}, {{.Gender}}. The embedded struct itself is also accessible as {{.FrontMatter}} when you need to pass it to a template function.

Layouts

The layout front-matter field selects the template. gen sets this on all generated pages; hand-authored files must set it explicitly (or leave it blank for a plain content page with no sidebar).

Layout Template Used for
person person.html Person pages
place place.html Place pages
source source.html Source pages
citation citation.html Citation pages
family family.html Family pages
treeoverview treeoverview.html Tree overview/index
chartancestors chartancestors.html Ancestor SVG chart
calendar calendar.html Monthly event calendar
listpeople listpeople.html Alphabetical people list
listsurnames listsurnames.html Surnames list
listplaces listplaces.html Places list
listsources listsources.html Sources list
listtodo listtodo.html Research to-do list
listchanges listchanges.html Recently updated pages
listanomalies listanomalies.html Data anomalies
listinferences listinferences.html Inferences
listfamilies listfamilies.html Families list
listfamilylines listfamilylines.html Family lines list
listtrees listtrees.html All trees
home home.html Site home page
diary diary.html Diary section index and year index pages
single single.html Simple content pages (stories, diary entries, tag pages)
list list.html Generic paginated list
search search.html Search page
(empty) plain.html Plain content, no sidebar

Shared partials (base.html)

Layout templates compose these named blocks via {{template "name" .}}:

Partial Description
head <head> element: charset, title, meta description, viewport, CSS links
site-header Top nav: home, trees, diary, stories, tags
tree-header Like site-header plus a per-tree nav row (people, surnames, places, to-do, recent updates) when .Tree.BasePath is set
footer Page footer with last-modified date when .LastMod is set
scripts JavaScript includes
featureimage Sidebar image: real photo when .Image is set; otherwise best available generic image from content/images/ via cascade; nothing when no match; shows "representative image" caption on person pages with a generic silhouette
tags Sidebar tag list linking to /tags/<slug>/
pagination Previous/next/first/last nav for paginated list pages

Template functions

Function Description
urlize s Lowercase and replace spaces with hyphens — used to build tag URL slugs
ukdate s Format YYYY-MM-DD as 2 January 2006; returns input unchanged if unparseable
featureImageSrc fm Given a FrontMatter value, return the best available feature image URL by checking content/images/; returns "" when nothing matches

Customising templates

Override the embedded templates by passing --assets <dir> to build, where <dir> contains a templates/ subdirectory with replacement .html files. Only the files you provide are replaced; all others use the built-in versions.

To add new template functions, add them to buildSiteTemplates in genster/build/template.go and reference them from a template file.


GEDCOM conventions

Genster understands several Ancestry-specific GEDCOM extensions:

  • _APID — Ancestry source citation identifier, translated to an Ancestry URL
  • _TREE — Ancestry tree reference in the GEDCOM header

Custom EVEN fact labels with specific handling:

  • Nickname — preferred nickname for the person
  • OLB — one-line biography: a short sentence summarising the person's life

Events whose values begin with certain phrases are included verbatim in the generated narrative:

  • He was recorded as
  • She was recorded as
  • It was recorded that

License

This is free and unencumbered software released into the public domain. For more information, see http://unlicense.org/ or the accompanying UNLICENSE file.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors