Repository: adrienverge/familytreemaker
Branch: master
Commit: 8f9e5c5d488f
Files: 3
Total size: 11.4 KB
Directory structure:
gitextract_0bngbhxa/
├── LouisXIVfamily.txt
├── README.md
└── familytreemaker.py
================================================
FILE CONTENTS
================================================
================================================
FILE: LouisXIVfamily.txt
================================================
# This file is an example to show how to format a family.
# Two lines represent an union:
Louis XIV (M, birthday=1638-09-05, deathday=1715-09-01)
Marie-Thérèse d'Autriche (F)
# Indented lines after the union represent children
Louis de France (id=Louis1661, M, birthday=1661-11-01, deathday=1711-04-14)
Marie-Thérèse\nde France (F, surname=la Petite Madame, birthday=1667, deathday=1672)
Philippe-Charles\nde France (M, surname=Duc d'Anjou, birthday=1668-08-05)
# Another union (2 parents + 3 children), father is one the previous union's children:
Louis de France (id=Louis1661)
Marie Anne\nChristine\nde Bavière (F)
Louis de France (id=Louis1682, M, birthday=1682, deathday=1712-02-19, surname=duc de Bourgogne)
Philippe (M, birthday=1683, deathday=1746, surname=roi d'Espagne\nsous le nom de\nPhilippe V)
Charles (M, birthday=1686-07-31)
# When several persons have the same name, ids can be used to differentiate them.
Louis de France (id=Louis1682)
Marie-Adélaïde\nde Savoie (F, deathday=1712-02-12)
Louis XV (M, id=LouisXV, birthday=1710-02-15, deathday=1774-05-10)
================================================
FILE: README.md
================================================
familytreemaker
===============
This program creates family tree graphs from simple text files.
The input file format is very simple, you describe persons of your family line
by line, children just have to follow parents in the file. Persons can be
repeated as long as they keep the same name or id. An example is given in the
file `LouisXIVfamily.txt`.
Installation
------------
Simply clone the repo.
This script outputs a graph descriptor in DOT format. To make the image
containing the graph, you will need a graph drawer such as [GraphViz] [1].
[1]: http://www.graphviz.org/ "GraphViz"
Usage
-----
The sample family descriptor `LouisXIVfamily.txt` is here to show you the
usage. Simply run:
```
$ ./familytreemaker.py -a 'Louis XIV' LouisXIVfamily.txt | dot -Tpng -o LouisXIVfamily.png
```
It will generate the tree from the infos in `LouisXIVfamily.txt`, starting from
*Louis XIV* and saving the image in `LouisXIVfamily.png`.
You can see the result:

================================================
FILE: familytreemaker.py
================================================
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (C) 2013 Adrien Vergé
"""familytreemaker
This program creates family tree graphs from simple text files.
The input file format is very simple, you describe persons of your family line
by line, children just have to follow parents in the file. Persons can be
repeated as long as they keep the same name or id. An example is given in the
file LouisXIVfamily.txt.
This script outputs a graph descriptor in DOT format. To make the image
containing the graph, you will need a graph drawer such as GraphViz.
For instance:
$ ./familytreemaker.py -a 'Louis XIV' LouisXIVfamily.txt | \
dot -Tpng -o LouisXIVfamily.png
will generate the tree from the infos in LouisXIVfamily.txt, starting from
Louis XIV and saving the image in LouisXIVfamily.png.
"""
__author__ = "Adrien Vergé"
__copyright__ = "Copyright 2013, Adrien Vergé"
__license__ = "GPL"
__version__ = "1.0"
import argparse
import random
import re
import sys
class Person:
"""This class represents a person.
Characteristics:
- name real name of the person
- id unique ID to be distinguished in a dictionnary
- attr attributes (e.g. gender, birth date...)
- households list of households this person belongs to
- follow_kids boolean to tell the algorithm to display this person's
descendent or not
"""
def __init__(self, desc):
self.attr = {}
self.parents = []
self.households = []
desc = desc.strip()
if '(' in desc and ')' in desc:
self.name, attr = desc[0:-1].split('(')
self.name = self.name.strip()
attr = map(lambda x: x.strip(), attr.split(','))
for a in attr:
if '=' in a:
k, v = a.split('=')
self.attr[k] = v
else:
self.attr[a] = True
else:
self.name = desc
if 'id' in self.attr:
self.id = self.attr['id']
else:
self.id = re.sub('[^0-9A-Za-z]', '', self.name)
if 'unique' in self.attr:
self.id += str(random.randint(100, 999))
self.follow_kids = True
def __str__(self):
return self.name
def dump(self):
return 'Person: %s (%s)\n' % (self.name, str(self.attr)) + \
' %d households' % len(self.households)
def graphviz(self):
label = self.name
if 'surname' in self.attr:
label += '\\n« ' + str(self.attr['surname']) + '»'
if 'birthday' in self.attr:
label += '\\n' + str(self.attr['birthday'])
if 'deathday' in self.attr:
label += ' † ' + str(self.attr['deathday'])
elif 'deathday' in self.attr:
label += '\\n† ' + str(self.attr['deathday'])
if 'notes' in self.attr:
label += '\\n' + str(self.attr['notes'])
opts = ['label="' + label + '"']
opts.append('style=filled')
opts.append('fillcolor=' + ('F' in self.attr and 'bisque' or
('M' in self.attr and 'azure2' or 'white')))
return self.id + '[' + ','.join(opts) + ']'
class Household:
"""This class represents a household, i.e. a union of two person.
Those two persons are listed in 'parents'. If they have children, they are
listed in 'kids'.
"""
def __init__(self):
self.parents = []
self.kids = []
self.id = 0
def __str__(self):
return 'Family:\n' + \
'\tparents = ' + ', '.join(map(str, self.parents)) + '\n' \
'\tchildren = ' + ', '.join(map(str, self.kids))
def isempty(self):
if len(self.parents) == 0 and len(self.kids) == 0:
return True
return False
class Family:
"""Represents the whole family.
'everybody' contains all persons, indexed by their unique id
'households' is the list of all unions (with or without children)
"""
everybody = {}
households = []
invisible = '[shape=circle,label="",height=0.01,width=0.01]';
def add_person(self, string):
"""Adds a person to self.everybody, or update his/her info if this
person already exists.
"""
p = Person(string)
key = p.id
if key in self.everybody:
self.everybody[key].attr.update(p.attr)
else:
self.everybody[key] = p
return self.everybody[key]
def add_household(self, h):
"""Adds a union (household) to self.households, and updates the
family members infos about this union.
"""
if len(h.parents) != 2:
print('error: number of parents != 2')
return
h.id = len(self.households)
self.households.append(h)
for p in h.parents:
if not h in p.households:
p.households.append(h)
def find_person(self, name):
"""Tries to find a person matching the 'name' argument.
"""
# First, search in ids
if name in self.everybody:
return self.everybody[name]
# Ancestor not found in 'id', maybe it's in the 'name' field?
for p in self.everybody.values():
if p.name == name:
return p
return None
def populate(self, f):
"""Reads the input file line by line, to find persons and unions.
"""
h = Household()
while True:
line = f.readline()
if line == '': # end of file
if not h.isempty():
self.add_household(h)
break
line = line.rstrip()
if line == '':
if not h.isempty():
self.add_household(h)
h = Household()
elif line[0] == '#':
continue
else:
if line[0] == '\t':
p = self.add_person(line[1:])
p.parents = h.parents
h.kids.append(p)
else:
p = self.add_person(line)
h.parents.append(p)
def find_first_ancestor(self):
"""Returns the first ancestor found.
A person is considered an ancestor if he/she has no parents.
This function is not very good, because we can have many persons with
no parents, it will always return the first found. A better practice
would be to return the one with the highest number of descendant.
"""
for p in self.everybody.values():
if len(p.parents) == 0:
return p
def next_generation(self, gen):
"""Takes the generation N in argument, returns the generation N+1.
Generations are represented as a list of persons.
"""
next_gen = []
for p in gen:
if not p.follow_kids:
continue
for h in p.households:
next_gen.extend(h.kids)
# append mari/femme
return next_gen
def get_spouse(household, person):
"""Returns the spouse or husband of a person in a union.
"""
return household.parents[0] == person \
and household.parents[1] or household.parents[0]
def display_generation(self, gen):
"""Outputs an entire generation in DOT format.
"""
# Display persons
print('\t{ rank=same;')
prev = None
for p in gen:
l = len(p.households)
if prev:
if l <= 1:
print('\t\t%s -> %s [style=invis];' % (prev, p.id))
else:
print('\t\t%s -> %s [style=invis];'
% (prev, Family.get_spouse(p.households[0], p).id))
if l == 0:
prev = p.id
continue
elif len(p.households) > 2:
raise Exception('Person "' + p.name + '" has more than 2 ' +
'spouses/husbands: drawing this is not ' +
'implemented')
# Display those on the left (if any)
for i in range(0, int(l/2)):
h = p.households[i]
spouse = Family.get_spouse(h, p)
print('\t\t%s -> h%d -> %s;' % (spouse.id, h.id, p.id))
print('\t\th%d%s;' % (h.id, Family.invisible))
# Display those on the right (at least one)
for i in range(int(l/2), l):
h = p.households[i]
spouse = Family.get_spouse(h, p)
print('\t\t%s -> h%d -> %s;' % (p.id, h.id, spouse.id))
print('\t\th%d%s;' % (h.id, Family.invisible))
prev = spouse.id
print('\t}')
# Display lines below households
print('\t{ rank=same;')
prev = None
for p in gen:
for h in p.households:
if len(h.kids) == 0:
continue
if prev:
print('\t\t%s -> h%d_0 [style=invis];' % (prev, h.id))
l = len(h.kids)
if l % 2 == 0:
# We need to add a node to keep symmetry
l += 1
print('\t\t' + ' -> '.join(map(lambda x: 'h%d_%d' % (h.id, x), range(l))) + ';')
for i in range(l):
print('\t\th%d_%d%s;' % (h.id, i, Family.invisible))
prev = 'h%d_%d' % (h.id, i)
print('\t}')
for p in gen:
for h in p.households:
if len(h.kids) > 0:
print('\t\th%d -> h%d_%d;'
% (h.id, h.id, int(len(h.kids)/2)))
i = 0
for c in h.kids:
print('\t\th%d_%d -> %s;'
% (h.id, i, c.id))
i += 1
if i == len(h.kids)/2:
i += 1
def output_descending_tree(self, ancestor):
"""Outputs the whole descending family tree from a given ancestor,
in DOT format.
"""
# Find the first households
gen = [ancestor]
print('digraph {\n' + \
'\tnode [shape=box];\n' + \
'\tedge [dir=none];\n')
for p in self.everybody.values():
print('\t' + p.graphviz() + ';')
print('')
while gen:
self.display_generation(gen)
gen = self.next_generation(gen)
print('}')
def main():
"""Entry point of the program when called as a script.
"""
# Parse command line options
parser = argparse.ArgumentParser(description=
'Generates a family tree graph from a simple text file')
parser.add_argument('-a', dest='ancestor',
help='make the family tree from an ancestor (if '+
'omitted, the program will try to find an ancestor)')
parser.add_argument('input', metavar='INPUTFILE',
help='the formatted text file representing the family')
args = parser.parse_args()
# Create the family
family = Family()
# Populate the family
f = open(args.input, 'r', encoding='utf-8')
family.populate(f)
f.close()
# Find the ancestor from whom the tree is built
if args.ancestor:
ancestor = family.find_person(args.ancestor)
if not ancestor:
raise Exception('Cannot find person "' + args.ancestor + '"')
else:
ancestor = family.find_first_ancestor()
# Output the graph descriptor, in DOT format
family.output_descending_tree(ancestor)
if __name__ == '__main__':
main()
gitextract_0bngbhxa/ ├── LouisXIVfamily.txt ├── README.md └── familytreemaker.py
SYMBOL INDEX (20 symbols across 1 files)
FILE: familytreemaker.py
class Person (line 37) | class Person:
method __init__ (line 50) | def __init__(self, desc):
method __str__ (line 78) | def __str__(self):
method dump (line 81) | def dump(self):
method graphviz (line 85) | def graphviz(self):
class Household (line 103) | class Household:
method __init__ (line 111) | def __init__(self):
method __str__ (line 116) | def __str__(self):
method isempty (line 121) | def isempty(self):
class Family (line 126) | class Family:
method add_person (line 139) | def add_person(self, string):
method add_household (line 154) | def add_household(self, h):
method find_person (line 170) | def find_person(self, name):
method populate (line 183) | def populate(self, f):
method find_first_ancestor (line 210) | def find_first_ancestor(self):
method next_generation (line 224) | def next_generation(self, gen):
method get_spouse (line 241) | def get_spouse(household, person):
method display_generation (line 248) | def display_generation(self, gen):
method output_descending_tree (line 322) | def output_descending_tree(self, ancestor):
function main (line 344) | def main():
Condensed preview — 3 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (13K chars).
[
{
"path": "LouisXIVfamily.txt",
"chars": 1082,
"preview": "# This file is an example to show how to format a family.\n\n# Two lines represent an union:\nLouis XIV (M, birthday=1638-0"
},
{
"path": "README.md",
"chars": 1020,
"preview": "familytreemaker\n===============\n\nThis program creates family tree graphs from simple text files.\n\nThe input file format "
},
{
"path": "familytreemaker.py",
"chars": 9620,
"preview": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n# Copyright (C) 2013 Adrien Vergé\n\n\"\"\"familytreemaker\n\nThis program creat"
}
]
About this extraction
This page contains the full source code of the adrienverge/familytreemaker GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 3 files (11.4 KB), approximately 3.5k tokens, and a symbol index with 20 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.