- Add black formating (see https://github.com/python/black)
authorBenoît Fontaine <benoit.fontaine@acft.fr>
Thu, 16 May 2019 09:39:17 +0000 (11:39 +0200)
committerBenoît Fontaine <benoit.fontaine@acft.fr>
Thu, 16 May 2019 09:39:17 +0000 (11:39 +0200)
- Add docstrings

.gitignore
__init__.py
fstogedcom.py
getmyancestors.py
mergemyancestors.py
translation.py

index d4ab07c65b9b051a02f90f468a2b98c9fc08a7e5..0ba9ab0d6e17ddb4367f5acb8106da1df0b0a31f 100644 (file)
@@ -1,5 +1,6 @@
 *.pyc
 *.ged
 *.log
+*.settings
 .vscode/
 scratchpad.py
index 56fafa58b3f43decb7699b93048b8b87e0f695aa..ec1fe35b778445937366c56db2d1de74c04e0a31 100644 (file)
@@ -1,2 +1,2 @@
 #!/usr/bin/env python3
-# -*- coding: utf-8 -*-
+# coding: utf-8
index 712d48a68899b5944a4258c613ff3cd8cda24843..af98196f2f31c64fc8933776c47624aec0883e32 100644 (file)
@@ -1,17 +1,17 @@
 #!/usr/bin/env python3
-# -*- coding: utf-8 -*-
+# coding: utf-8
 
 # global import
+import re
+import os
+import sys
+import time
+import asyncio
+import tempfile
 from tkinter import Tk, StringVar, IntVar, filedialog, messagebox, Menu, TclError, PhotoImage
 from tkinter.ttk import Frame, Label, Entry, Button, Checkbutton, Treeview, Notebook
 from threading import Thread
 from diskcache import Cache
-import time
-import tempfile
-import asyncio
-import re
-import os
-import sys
 
 # local import
 from getmyancestors import Session, Tree, Indi, Fam
@@ -19,10 +19,9 @@ from mergemyancestors import Gedcom
 from translation import translations
 
 
-tmp_dir = os.path.join(tempfile.gettempdir(), 'fstogedcom')
-global cache
+tmp_dir = os.path.join(tempfile.gettempdir(), "fstogedcom")
 cache = Cache(tmp_dir)
-lang = cache.get('lang')
+lang = cache.get("lang")
 
 
 def _(string):
@@ -31,104 +30,138 @@ def _(string):
     return string
 
 
-# Entry widget with right-clic menu to copy/cut/paste
 class EntryWithMenu(Entry):
+    """ Entry widget with right-clic menu to copy/cut/paste """
+
     def __init__(self, master, **kw):
         super(EntryWithMenu, self).__init__(master, **kw)
-        self.bind('<Button-3>', self.click_right)
+        self.bind("<Button-3>", self.click_right)
 
     def click_right(self, event):
+        """ open menu """
         menu = Menu(self, tearoff=0)
         try:
             self.selection_get()
-            state = 'normal'
+            state = "normal"
         except TclError:
-            state = 'disabled'
-        menu.add_command(label=_('Copy'), command=self.copy, state=state)
-        menu.add_command(label=_('Cut'), command=self.cut, state=state)
-        menu.add_command(label=_('Paste'), command=self.paste)
+            state = "disabled"
+        menu.add_command(label=_("Copy"), command=self.copy, state=state)
+        menu.add_command(label=_("Cut"), command=self.cut, state=state)
+        menu.add_command(label=_("Paste"), command=self.paste)
         menu.post(event.x_root, event.y_root)
 
     def copy(self):
+        """ copy in clipboard """
         self.clipboard_clear()
         text = self.selection_get()
         self.clipboard_append(text)
 
     def cut(self):
+        """ move in clipboard """
         self.copy()
-        self.delete('sel.first', 'sel.last')
+        self.delete("sel.first", "sel.last")
 
     def paste(self):
+        """ paste from clipboard """
         try:
-            text = self.selection_get(selection='CLIPBOARD')
-            self.insert('insert', text)
+            text = self.selection_get(selection="CLIPBOARD")
+            self.insert("insert", text)
         except TclError:
             pass
 
 
-# List of files to merge
 class FilesToMerge(Treeview):
+    """ List of GEDCOM files to merge """
+
     def __init__(self, master, **kwargs):
-        super(FilesToMerge, self).__init__(master, selectmode='extended', height=5, **kwargs)
-        self.heading('#0', text=_('Files'))
-        self.column('#0', width=300)
+        super(FilesToMerge, self).__init__(master, selectmode="extended", height=5, **kwargs)
+        self.heading("#0", text=_("Files"))
+        self.column("#0", width=300)
         self.files = dict()
-        self.bind('<Button-3>', self.popup)
+        self.bind("<Button-3>", self.popup)
 
     def add_file(self, filename):
+        """ add a GEDCOM file """
         if any(f.name == filename for f in self.files.values()):
-            messagebox.showinfo(_('Error'), message=_('File already exist: ') + os.path.basename(filename))
+            messagebox.showinfo(
+                _("Error"), message=_("File already exist: ") + os.path.basename(filename)
+            )
             return
         if not os.path.exists(filename):
-            messagebox.showinfo(_('Error'), message=_('File not found: ') + os.path.basename(filename))
+            messagebox.showinfo(
+                _("Error"), message=_("File not found: ") + os.path.basename(filename)
+            )
             return
-        file = open(filename, 'r', encoding='utf-8')
-        new_id = self.insert('', 0, text=os.path.basename(filename))
+        file = open(filename, "r", encoding="utf-8")
+        new_id = self.insert("", 0, text=os.path.basename(filename))
         self.files[new_id] = file
 
     def popup(self, event):
+        """ open menu to remove item """
         item = self.identify_row(event.y)
         if item:
             menu = Menu(self, tearoff=0)
-            menu.add_command(label=_('Remove'), command=self.delete_item(item))
+            menu.add_command(label=_("Remove"), command=self.delete_item(item))
             menu.post(event.x_root, event.y_root)
 
     def delete_item(self, item):
+        """ return a function to remove a file """
+
         def delete():
             self.files[item].close()
             self.files.pop(item)
             self.delete(item)
+
         return delete
 
 
-# Merge widget
 class Merge(Frame):
+    """ Merge GEDCOM widget """
 
     def __init__(self, master, **kwargs):
         super(Merge, self).__init__(master, **kwargs)
-        warning = Label(self, font=('a', 7), wraplength=300, justify='center', text=_('Warning: This tool should only be used to merge GEDCOM files from this software. If you use other GEDCOM files, the result is not guaranteed.'))
+        warning = Label(
+            self,
+            font=("a", 7),
+            wraplength=300,
+            justify="center",
+            text=_(
+                "Warning: This tool should only be used to merge GEDCOM files from this software. "
+                "If you use other GEDCOM files, the result is not guaranteed."
+            ),
+        )
         self.files_to_merge = FilesToMerge(self)
-        self.btn_add_file = Button(self, text=_('Add files'), command=self.add_files)
+        self.btn_add_file = Button(self, text=_("Add files"), command=self.add_files)
         buttons = Frame(self, borderwidth=20)
-        self.btn_quit = Button(buttons, text=_('Quit'), command=self.quit)
-        self.btn_save = Button(buttons, text=_('Merge'), command=self.save)
+        self.btn_quit = Button(buttons, text=_("Quit"), command=self.quit)
+        self.btn_save = Button(buttons, text=_("Merge"), command=self.save)
         warning.pack()
         self.files_to_merge.pack()
         self.btn_add_file.pack()
-        self.btn_quit.pack(side='left', padx=(0, 40))
-        self.btn_save.pack(side='right', padx=(40, 0))
-        buttons.pack(side='bottom')
+        self.btn_quit.pack(side="left", padx=(0, 40))
+        self.btn_save.pack(side="right", padx=(40, 0))
+        buttons.pack(side="bottom")
 
     def add_files(self):
-        for filename in filedialog.askopenfilenames(title=_('Open'), defaultextension='.ged', filetypes=(('GEDCOM', '.ged'), (_('All files'), '*.*'))):
+        """ open file explorer to pick a file """
+        for filename in filedialog.askopenfilenames(
+            title=_("Open"),
+            defaultextension=".ged",
+            filetypes=(("GEDCOM", ".ged"), (_("All files"), "*.*")),
+        ):
             self.files_to_merge.add_file(filename)
 
     def save(self):
+        """ merge GEDCOM files """
         if not self.files_to_merge.files:
-            messagebox.showinfo(_('Error'), message=_('Please add GEDCOM files'))
+            messagebox.showinfo(_("Error"), message=_("Please add GEDCOM files"))
             return
 
-        filename = filedialog.asksaveasfilename(title=_('Save as'), defaultextension='.ged', filetypes=(('GEDCOM', '.ged'), (_('All files'), '*.*')))
+        filename = filedialog.asksaveasfilename(
+            title=_("Save as"),
+            defaultextension=".ged",
+            filetypes=(("GEDCOM", ".ged"), (_("All files"), "*.*")),
+        )
         tree = Tree()
 
         indi_counter = 0
@@ -191,86 +224,99 @@ class Merge(Frame):
 
         # compute number for family relationships and print GEDCOM file
         tree.reset_num()
-        with open(filename, 'w', encoding='utf-8') as file:
+        with open(filename, "w", encoding="utf-8") as file:
             tree.print(file)
-        messagebox.showinfo(_('Info'), message=_('Files successfully merged'))
+        messagebox.showinfo(_("Info"), message=_("Files successfully merged"))
 
-    # prevent exception on quit during download
     def quit(self):
+        """ prevent exception on quit during download """
         super(Merge, self).quit()
         os._exit(1)
 
 
-# Sign In widget
 class SignIn(Frame):
+    """ Sign In widget """
 
     def __init__(self, master, **kwargs):
         super(SignIn, self).__init__(master, **kwargs)
         self.username = StringVar()
         self.password = StringVar()
-        label_username = Label(self, text=_('Username:'))
+        label_username = Label(self, text=_("Username:"))
         entry_username = EntryWithMenu(self, textvariable=self.username, width=30)
-        label_password = Label(self, text=_('Password:'))
-        entry_password = EntryWithMenu(self, show='●', textvariable=self.password, width=30)
+        label_password = Label(self, text=_("Password:"))
+        entry_password = EntryWithMenu(self, show="●", textvariable=self.password, width=30)
         label_username.grid(row=0, column=0, pady=15, padx=(0, 5))
         entry_username.grid(row=0, column=1)
         label_password.grid(row=1, column=0, padx=(0, 5))
         entry_password.grid(row=1, column=1)
         entry_username.focus_set()
-        entry_username.bind('<Key>', self.enter)
-        entry_password.bind('<Key>', self.enter)
+        entry_username.bind("<Key>", self.enter)
+        entry_password.bind("<Key>", self.enter)
 
     def enter(self, evt):
-        if evt.keysym in {'Return', 'KP_Enter'}:
+        """ enter event """
+        if evt.keysym in {"Return", "KP_Enter"}:
             self.master.master.command_in_thread(self.master.master.login)()
 
 
-# List of starting individuals
 class StartIndis(Treeview):
+    """ List of starting individuals """
+
     def __init__(self, master, **kwargs):
-        super(StartIndis, self).__init__(master, selectmode='extended', height=5, columns=('fid',), **kwargs)
-        self.heading('#0', text=_('Name'))
-        self.column('#0', width=250)
-        self.column('fid', width=80)
+        super(StartIndis, self).__init__(
+            master, selectmode="extended", height=5, columns=("fid",), **kwargs
+        )
+        self.heading("#0", text=_("Name"))
+        self.column("#0", width=250)
+        self.column("fid", width=80)
         self.indis = dict()
-        self.heading('fid', text='Id')
-        self.bind('<Button-3>', self.popup)
+        self.heading("fid", text="Id")
+        self.bind("<Button-3>", self.popup)
 
     def add_indi(self, fid):
+        """ add an individual fid """
         if not fid:
-            return
+            return None
         if fid in self.indis.values():
-            messagebox.showinfo(_('Error'), message=_('ID already exist'))
-            return
-        if not re.match(r'[A-Z0-9]{4}-[A-Z0-9]{3}', fid):
-            messagebox.showinfo(_('Error'), message=_('Invalid FamilySearch ID: ') + fid)
-            return
+            messagebox.showinfo(_("Error"), message=_("ID already exist"))
+            return None
+        if not re.match(r"[A-Z0-9]{4}-[A-Z0-9]{3}", fid):
+            messagebox.showinfo(_("Error"), message=_("Invalid FamilySearch ID: ") + fid)
+            return None
         fs = self.master.master.master.fs
-        data = fs.get_url('/platform/tree/persons/%s.json' % fid)
-        if data and 'persons' in data:
-            if 'names' in data['persons'][0]:
-                for name in data['persons'][0]['names']:
-                    if name['preferred']:
-                        self.indis[self.insert('', 0, text=name['nameForms'][0]['fullText'], values=fid)] = fid
+        data = fs.get_url("/platform/tree/persons/%s.json" % fid)
+        if data and "persons" in data:
+            if "names" in data["persons"][0]:
+                for name in data["persons"][0]["names"]:
+                    if name["preferred"]:
+                        self.indis[
+                            self.insert("", 0, text=name["nameForms"][0]["fullText"], values=fid)
+                        ] = fid
                         return True
-        messagebox.showinfo(_('Error'), message=_('Individual not found'))
+        messagebox.showinfo(_("Error"), message=_("Individual not found"))
+        return None
 
     def popup(self, event):
+        """ open menu to remove item """
         item = self.identify_row(event.y)
         if item:
             menu = Menu(self, tearoff=0)
-            menu.add_command(label=_('Remove'), command=self.delete_item(item))
+            menu.add_command(label=_("Remove"), command=self.delete_item(item))
             menu.post(event.x_root, event.y_root)
 
     def delete_item(self, item):
+        """ return a function to remove a fid """
+
         def delete():
             self.indis.pop(item)
             self.delete(item)
+
         return delete
 
 
-# Options form
 class Options(Frame):
+    """ Options form """
+
     def __init__(self, master, ordinances=False, **kwargs):
         super(Options, self).__init__(master, **kwargs)
         self.ancestors = IntVar()
@@ -283,40 +329,49 @@ class Options(Frame):
         self.fid = StringVar()
         btn = Frame(self)
         entry_fid = EntryWithMenu(btn, textvariable=self.fid, width=16)
-        entry_fid.bind('<Key>', self.enter)
-        label_ancestors = Label(self, text=_('Number of generations to ascend'))
+        entry_fid.bind("<Key>", self.enter)
+        label_ancestors = Label(self, text=_("Number of generations to ascend"))
         entry_ancestors = EntryWithMenu(self, textvariable=self.ancestors, width=5)
-        label_descendants = Label(self, text=_('Number of generations to descend'))
+        label_descendants = Label(self, text=_("Number of generations to descend"))
         entry_descendants = EntryWithMenu(self, textvariable=self.descendants, width=5)
-        btn_add_indi = Button(btn, text=_('Add a FamilySearch ID'), command=self.add_indi)
-        btn_spouses = Checkbutton(self, text='\t' + _('Add spouses and couples information'), variable=self.spouses)
-        btn_ordinances = Checkbutton(self, text='\t' + _('Add Temple information'), variable=self.ordinances)
-        btn_contributors = Checkbutton(self, text='\t' + _('Add list of contributors in notes'), variable=self.contributors)
+        btn_add_indi = Button(btn, text=_("Add a FamilySearch ID"), command=self.add_indi)
+        btn_spouses = Checkbutton(
+            self, text="\t" + _("Add spouses and couples information"), variable=self.spouses
+        )
+        btn_ordinances = Checkbutton(
+            self, text="\t" + _("Add Temple information"), variable=self.ordinances
+        )
+        btn_contributors = Checkbutton(
+            self, text="\t" + _("Add list of contributors in notes"), variable=self.contributors
+        )
         self.start_indis.grid(row=0, column=0, columnspan=3)
-        entry_fid.grid(row=0, column=0, sticky='w')
-        btn_add_indi.grid(row=0, column=1, sticky='w')
-        btn.grid(row=1, column=0, columnspan=2, sticky='w')
-        entry_ancestors.grid(row=2, column=0, sticky='w')
-        label_ancestors.grid(row=2, column=1, sticky='w')
-        entry_descendants.grid(row=3, column=0, sticky='w')
-        label_descendants.grid(row=3, column=1, sticky='w')
-        btn_spouses.grid(row=4, column=0, columnspan=2, sticky='w')
+        entry_fid.grid(row=0, column=0, sticky="w")
+        btn_add_indi.grid(row=0, column=1, sticky="w")
+        btn.grid(row=1, column=0, columnspan=2, sticky="w")
+        entry_ancestors.grid(row=2, column=0, sticky="w")
+        label_ancestors.grid(row=2, column=1, sticky="w")
+        entry_descendants.grid(row=3, column=0, sticky="w")
+        label_descendants.grid(row=3, column=1, sticky="w")
+        btn_spouses.grid(row=4, column=0, columnspan=2, sticky="w")
         if ordinances:
-            btn_ordinances.grid(row=5, column=0, columnspan=3, sticky='w')
-        btn_contributors.grid(row=6, column=0, columnspan=3, sticky='w')
+            btn_ordinances.grid(row=5, column=0, columnspan=3, sticky="w")
+        btn_contributors.grid(row=6, column=0, columnspan=3, sticky="w")
         entry_ancestors.focus_set()
 
     def add_indi(self):
+        """ add a fid """
         if self.start_indis.add_indi(self.fid.get()):
-            self.fid.set('')
+            self.fid.set("")
 
     def enter(self, evt):
-        if evt.keysym in {'Return', 'KP_Enter'}:
+        """ enter event """
+        if evt.keysym in {"Return", "KP_Enter"}:
             self.add_indi()
 
 
-# Main widget
 class Download(Frame):
+    """ Main widget """
+
     def __init__(self, master, **kwargs):
         super(Download, self).__init__(master, borderwidth=20, **kwargs)
         self.fs = None
@@ -327,7 +382,9 @@ class Download(Frame):
         self.info_tree = False
         self.start_time = None
         info = Frame(self, borderwidth=10)
-        self.info_label = Label(info, wraplength=350, borderwidth=20, justify='center', font=('a', 10, 'bold'))
+        self.info_label = Label(
+            info, wraplength=350, borderwidth=20, justify="center", font=("a", 10, "bold")
+        )
         self.info_indis = Label(info)
         self.info_fams = Label(info)
         self.info_sources = Label(info)
@@ -343,81 +400,104 @@ class Download(Frame):
         self.form = Frame(self)
         self.sign_in = SignIn(self.form)
         self.options = None
-        self.title = Label(self, text=_('Sign In to FamilySearch'), font=('a', 12, 'bold'))
+        self.title = Label(self, text=_("Sign In to FamilySearch"), font=("a", 12, "bold"))
         buttons = Frame(self)
-        self.btn_quit = Button(buttons, text=_('Quit'), command=Thread(target=self.quit).start)
-        self.btn_valid = Button(buttons, text=_('Sign In'), command=self.command_in_thread(self.login))
+        self.btn_quit = Button(buttons, text=_("Quit"), command=Thread(target=self.quit).start)
+        self.btn_valid = Button(
+            buttons, text=_("Sign In"), command=self.command_in_thread(self.login)
+        )
         self.title.pack()
         self.sign_in.pack()
         self.form.pack()
-        self.btn_quit.pack(side='left', padx=(0, 40))
-        self.btn_valid.pack(side='right', padx=(40, 0))
+        self.btn_quit.pack(side="left", padx=(0, 40))
+        self.btn_valid.pack(side="right", padx=(40, 0))
         info.pack()
-        buttons.pack(side='bottom')
+        buttons.pack(side="bottom")
         self.pack()
         self.update_needed = False
 
     def info(self, text):
+        """ dislay informations """
         self.info_label.config(text=text)
 
     def save(self):
-        filename = filedialog.asksaveasfilename(title=_('Save as'), defaultextension='.ged', filetypes=(('GEDCOM', '.ged'), (_('All files'), '*.*')))
+        """ save the GEDCOM file """
+        filename = filedialog.asksaveasfilename(
+            title=_("Save as"),
+            defaultextension=".ged",
+            filetypes=(("GEDCOM", ".ged"), (_("All files"), "*.*")),
+        )
         if not filename:
             return
-        with open(filename, 'w', encoding='utf-8') as file:
+        with open(filename, "w", encoding="utf-8") as file:
             self.tree.print(file)
 
     def login(self):
+        """ log in FamilySearch """
         global _
         username = self.sign_in.username.get()
         password = self.sign_in.password.get()
         if not (username and password):
-            messagebox.showinfo(message=_('Please enter your FamilySearch username and password.'))
+            messagebox.showinfo(message=_("Please enter your FamilySearch username and password."))
             return
-        self.btn_valid.config(state='disabled')
-        self.info(_('Login to FamilySearch...'))
-        self.logfile = open('download.log', 'w', encoding='utf-8')
-        self.fs = Session(self.sign_in.username.get(), self.sign_in.password.get(), verbose=True, logfile=self.logfile, timeout=1)
+        self.btn_valid.config(state="disabled")
+        self.info(_("Login to FamilySearch..."))
+        self.logfile = open("download.log", "w", encoding="utf-8")
+        self.fs = Session(
+            self.sign_in.username.get(),
+            self.sign_in.password.get(),
+            verbose=True,
+            logfile=self.logfile,
+            timeout=1,
+        )
         if not self.fs.logged:
-            messagebox.showinfo(_('Error'), message=_('The username or password was incorrect'))
-            self.btn_valid.config(state='normal')
-            self.info('')
+            messagebox.showinfo(_("Error"), message=_("The username or password was incorrect"))
+            self.btn_valid.config(state="normal")
+            self.info("")
             return
         self.tree = Tree(self.fs)
         _ = self.fs._
-        self.title.config(text=_('Options'))
-        cache.delete('lang')
-        cache.add('lang', self.fs.lang)
-        lds_account = self.fs.get_url('/platform/tree/persons/%s/ordinances.json' % self.fs.get_userid()) != 'error'
+        self.title.config(text=_("Options"))
+        cache.delete("lang")
+        cache.add("lang", self.fs.lang)
+        lds_account = (
+            self.fs.get_url("/platform/tree/persons/%s/ordinances.json" % self.fs.get_userid())
+            != "error"
+        )
         self.options = Options(self.form, lds_account)
-        self.info('')
+        self.info("")
         self.sign_in.destroy()
         self.options.pack()
         self.master.change_lang()
-        self.btn_valid.config(command=self.command_in_thread(self.download), state='normal', text=_('Download'))
+        self.btn_valid.config(
+            command=self.command_in_thread(self.download), state="normal", text=_("Download")
+        )
         self.options.start_indis.add_indi(self.fs.get_userid())
         self.update_needed = False
 
     def quit(self):
+        """ prevent exception during download """
         self.update_needed = False
         if self.logfile:
             self.logfile.close()
         super(Download, self).quit()
-        # prevent exception during download
         os._exit(1)
 
     def download(self):
-        todo = [self.options.start_indis.indis[key] for key in sorted(self.options.start_indis.indis)]
+        """ download family tree """
+        todo = [
+            self.options.start_indis.indis[key] for key in sorted(self.options.start_indis.indis)
+        ]
         for fid in todo:
-            if not re.match(r'[A-Z0-9]{4}-[A-Z0-9]{3}', fid):
-                messagebox.showinfo(_('Error'), message=_('Invalid FamilySearch ID: ') + fid)
+            if not re.match(r"[A-Z0-9]{4}-[A-Z0-9]{3}", fid):
+                messagebox.showinfo(_("Error"), message=_("Invalid FamilySearch ID: ") + fid)
                 return
         self.start_time = time.time()
         self.options.destroy()
         self.form.destroy()
-        self.title.config(text='FamilySearch to GEDCOM')
-        self.btn_valid.config(state='disabled')
-        self.info(_('Downloading starting individuals...'))
+        self.title.config(text="FamilySearch to GEDCOM")
+        self.btn_valid.config(state="disabled")
+        self.info(_("Downloading starting individuals..."))
         self.info_tree = True
         self.tree.add_indis(todo)
         todo = set(todo)
@@ -426,7 +506,7 @@ class Download(Frame):
             if not todo:
                 break
             done |= todo
-            self.info(_('Downloading %s. of generations of ancestors...') % (i + 1))
+            self.info(_("Downloading %s. of generations of ancestors...") % (i + 1))
             todo = self.tree.add_parents(todo) - done
 
         todo = set(self.tree.indi.keys())
@@ -435,11 +515,11 @@ class Download(Frame):
             if not todo:
                 break
             done |= todo
-            self.info(_('Downloading %s. of generations of descendants...') % (i + 1))
+            self.info(_("Downloading %s. of generations of descendants...") % (i + 1))
             todo = self.tree.add_children(todo) - done
 
         if self.options.spouses.get():
-            self.info(_('Downloading spouses and marriage information...'))
+            self.info(_("Downloading spouses and marriage information..."))
             todo = set(self.tree.indi.keys())
             self.tree.add_spouses(todo)
         ordi = self.options.ordinances.get()
@@ -461,34 +541,44 @@ class Download(Frame):
                 await future
 
         loop = asyncio.get_event_loop()
-        self.info(_('Downloading notes') + (((',' if cont else _(' and')) + _(' ordinances')) if ordi else '') + (_(' and contributors') if cont else '') + '...')
+        self.info(
+            _("Downloading notes")
+            + ((("," if cont else _(" and")) + _(" ordinances")) if ordi else "")
+            + (_(" and contributors") if cont else "")
+            + "..."
+        )
         loop.run_until_complete(download_stuff(loop))
 
         self.tree.reset_num()
-        self.btn_valid.config(command=self.save, state='normal', text=_('Save'))
-        self.info(text=_('Success ! Click below to save your GEDCOM file'))
+        self.btn_valid.config(command=self.save, state="normal", text=_("Save"))
+        self.info(text=_("Success ! Click below to save your GEDCOM file"))
         self.update_info_tree()
         self.update_needed = False
 
     def command_in_thread(self, func):
+        """ command to update widget in a new Thread """
+
         def res():
             self.update_needed = True
             Thread(target=self.update_gui).start()
             Thread(target=func).start()
+
         return res
 
     def update_info_tree(self):
+        """ update informations """
         if self.info_tree and self.start_time and self.tree:
-            self.info_indis.config(text=_('Individuals: %s') % len(self.tree.indi))
-            self.info_fams.config(text=_('Families: %s') % len(self.tree.fam))
-            self.info_sources.config(text=_('Sources: %s') % len(self.tree.sources))
-            self.info_notes.config(text=_('Notes: %s') % len(self.tree.notes))
+            self.info_indis.config(text=_("Individuals: %s") % len(self.tree.indi))
+            self.info_fams.config(text=_("Families: %s") % len(self.tree.fam))
+            self.info_sources.config(text=_("Sources: %s") % len(self.tree.sources))
+            self.info_notes.config(text=_("Notes: %s") % len(self.tree.notes))
             t = round(time.time() - self.start_time)
             minutes = t // 60
             seconds = t % 60
-            self.time.config(text=_('Elapsed time: %s:%s') % (minutes, '00%s'[len(str(seconds)):] % seconds))
+            self.time.config(text=_("Elapsed time: %s:%s") % (minutes, str(seconds).zfill(2)))
 
     def update_gui(self):
+        """ update widget """
         while self.update_needed:
             self.update_info_tree()
             self.master.update()
@@ -496,27 +586,30 @@ class Download(Frame):
 
 
 class FStoGEDCOM(Notebook):
+    """ Main notebook """
+
     def __init__(self, master, **kwargs):
         super(FStoGEDCOM, self).__init__(master, width=400, **kwargs)
         self.download = Download(self)
         self.merge = Merge(self)
-        self.add(self.download, text=_('Download GEDCOM'))
-        self.add(self.merge, text=_('Merge GEDCOMs'))
+        self.add(self.download, text=_("Download GEDCOM"))
+        self.add(self.merge, text=_("Merge GEDCOMs"))
         self.pack()
 
     def change_lang(self):
-        self.tab(self.index(self.download), text=_('Download GEDCOM'))
-        self.tab(self.index(self.merge), text=_('Merge GEDCOMs'))
-        self.download.btn_quit.config(text=_('Quit'))
-        self.merge.btn_quit.config(text=_('Quit'))
-        self.merge.btn_save.config(text=_('Merge'))
-        self.merge.btn_add_file.config(text=_('Add files'))
+        """ update text with user's language """
+        self.tab(self.index(self.download), text=_("Download GEDCOM"))
+        self.tab(self.index(self.merge), text=_("Merge GEDCOMs"))
+        self.download.btn_quit.config(text=_("Quit"))
+        self.merge.btn_quit.config(text=_("Quit"))
+        self.merge.btn_save.config(text=_("Merge"))
+        self.merge.btn_add_file.config(text=_("Add files"))
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     root = Tk()
-    root.title('FamilySearch to GEDCOM')
-    if sys.platform != 'darwin':
-        root.iconphoto(True, PhotoImage(file='fstogedcom.png'))
+    root.title("FamilySearch to GEDCOM")
+    if sys.platform != "darwin":
+        root.iconphoto(True, PhotoImage(file="fstogedcom.png"))
     fstogedcom = FStoGEDCOM(root)
     fstogedcom.mainloop()
index 2f9f7dd5554b45661c9514cb82243b350e5b66c0..7e54b73167f59d1c45717475a0d5c7ca03ce8592 100755 (executable)
@@ -1,5 +1,5 @@
 #!/usr/bin/env python3
-# -*- coding: utf-8 -*-
+# coding: utf-8
 """
    getmyancestors.py - Retrieve GEDCOM data from FamilySearch Tree
    Copyright (C) 2014-2016 Giulio Genovese (giulio.genovese@gmail.com)
 
 # global import
 from __future__ import print_function
+import re
 import sys
-import argparse
-import getpass
 import time
+import getpass
 import asyncio
-import re
+import argparse
+import requests
 
 # local import
 from translation import translations
 
-try:
-    import requests
-except ImportError:
-    sys.stderr.write('You need to install the requests module first\n')
-    sys.stderr.write('(run this in your terminal: "python3 -m pip install requests" or "python3 -m pip install --user requests")\n')
-    exit(2)
-
 try:
     import babelfish
 except ImportError:
-    sys.stderr.write('You need to install the babelfish module first\n')
-    sys.stderr.write('(run this in your terminal: "python3 -m pip install babelfish" or "python3 -m pip install --user babelfish")\n')
-    exit(2)
+    sys.stderr.write("You need to install the babelfish module first\n")
+    sys.stderr.write(
+        '(run this in your terminal: "python3 -m pip install babelfish" '
+        'or "python3 -m pip install --user babelfish")\n'
+    )
+    sys.exit(2)
 
-MAX_PERSONS = 200  # is subject to change: see https://www.familysearch.org/developers/docs/api/tree/Persons_resource
+# is subject to change: see https://www.familysearch.org/developers/docs/api/tree/Persons_resource
+MAX_PERSONS = 200
 
 FACT_TAGS = {
-    'http://gedcomx.org/Birth': 'BIRT',
-    'http://gedcomx.org/Christening': 'CHR',
-    'http://gedcomx.org/Death': 'DEAT',
-    'http://gedcomx.org/Burial': 'BURI',
-    'http://gedcomx.org/PhysicalDescription': 'DSCR',
-    'http://gedcomx.org/Occupation': 'OCCU',
-    'http://gedcomx.org/MilitaryService': '_MILT',
-    'http://gedcomx.org/Marriage': 'MARR',
-    'http://gedcomx.org/Divorce': 'DIV',
-    'http://gedcomx.org/Annulment': 'ANUL',
-    'http://gedcomx.org/CommonLawMarriage': '_COML',
-    'http://gedcomx.org/BarMitzvah': 'BARM',
-    'http://gedcomx.org/BatMitzvah': 'BASM',
-    'http://gedcomx.org/Naturalization': 'NATU',
-    'http://gedcomx.org/Residence': 'RESI',
-    'http://gedcomx.org/Religion': 'RELI',
-    'http://familysearch.org/v1/TitleOfNobility': 'TITL',
-    'http://gedcomx.org/Cremation': 'CREM',
-    'http://gedcomx.org/Caste': 'CAST',
-    'http://gedcomx.org/Nationality': 'NATI',
+    "http://gedcomx.org/Birth": "BIRT",
+    "http://gedcomx.org/Christening": "CHR",
+    "http://gedcomx.org/Death": "DEAT",
+    "http://gedcomx.org/Burial": "BURI",
+    "http://gedcomx.org/PhysicalDescription": "DSCR",
+    "http://gedcomx.org/Occupation": "OCCU",
+    "http://gedcomx.org/MilitaryService": "_MILT",
+    "http://gedcomx.org/Marriage": "MARR",
+    "http://gedcomx.org/Divorce": "DIV",
+    "http://gedcomx.org/Annulment": "ANUL",
+    "http://gedcomx.org/CommonLawMarriage": "_COML",
+    "http://gedcomx.org/BarMitzvah": "BARM",
+    "http://gedcomx.org/BatMitzvah": "BASM",
+    "http://gedcomx.org/Naturalization": "NATU",
+    "http://gedcomx.org/Residence": "RESI",
+    "http://gedcomx.org/Religion": "RELI",
+    "http://familysearch.org/v1/TitleOfNobility": "TITL",
+    "http://gedcomx.org/Cremation": "CREM",
+    "http://gedcomx.org/Caste": "CAST",
+    "http://gedcomx.org/Nationality": "NATI",
 }
 
 FACT_EVEN = {
-    'http://gedcomx.org/Stillbirth': 'Stillborn',
-    'http://familysearch.org/v1/Affiliation': 'Affiliation',
-    'http://gedcomx.org/Clan': 'Clan Name',
-    'http://gedcomx.org/NationalId': 'National Identification',
-    'http://gedcomx.org/Ethnicity': 'Race',
-    'http://familysearch.org/v1/TribeName': 'Tribe Name'
+    "http://gedcomx.org/Stillbirth": "Stillborn",
+    "http://familysearch.org/v1/Affiliation": "Affiliation",
+    "http://gedcomx.org/Clan": "Clan Name",
+    "http://gedcomx.org/NationalId": "National Identification",
+    "http://gedcomx.org/Ethnicity": "Race",
+    "http://familysearch.org/v1/TribeName": "Tribe Name",
 }
 
 ORDINANCES_STATUS = {
-    'http://familysearch.org/v1/Ready': 'QUALIFIED',
-    'http://familysearch.org/v1/Completed': 'COMPLETED',
-    'http://familysearch.org/v1/Cancelled': 'CANCELED',
-    'http://familysearch.org/v1/InProgress': 'SUBMITTED',
-    'http://familysearch.org/v1/NotNeeded': 'INFANT'
+    "http://familysearch.org/v1/Ready": "QUALIFIED",
+    "http://familysearch.org/v1/Completed": "COMPLETED",
+    "http://familysearch.org/v1/Cancelled": "CANCELED",
+    "http://familysearch.org/v1/InProgress": "SUBMITTED",
+    "http://familysearch.org/v1/NotNeeded": "INFANT",
 }
 
 
 def cont(string):
+    """ parse a GEDCOM line adding CONT and CONT tags if necessary """
     level = int(string[:1]) + 1
     lines = string.splitlines()
     res = list()
@@ -98,21 +97,30 @@ def cont(string):
     for line in lines:
         c_line = line
         to_conc = list()
-        while len(c_line.encode('utf-8')) > max_len:
+        while len(c_line.encode("utf-8")) > max_len:
             index = min(max_len, len(c_line) - 2)
-            while (len(c_line[:index].encode('utf-8')) > max_len or re.search(r'[ \t\v]', c_line[index - 1:index + 1])) and index > 1:
+            while (
+                len(c_line[:index].encode("utf-8")) > max_len
+                or re.search(r"[ \t\v]", c_line[index - 1 : index + 1])
+            ) and index > 1:
                 index -= 1
             to_conc.append(c_line[:index])
             c_line = c_line[index:]
             max_len = 248
         to_conc.append(c_line)
-        res.append(('\n%s CONC ' % level).join(to_conc))
+        res.append(("\n%s CONC " % level).join(to_conc))
         max_len = 248
-    return ('\n%s CONT ' % level).join(res)
+    return ("\n%s CONT " % level).join(res) + "\n"
 
 
-# FamilySearch session class
 class Session:
+    """ Create a FamilySearch session
+        :param username and password: valid FamilySearch credentials
+        :param verbose: True to active verbose mode
+        :param logfile: a file object or similar
+        :param timeout: time before retry a request
+    """
+
     def __init__(self, username, password, verbose=False, logfile=sys.stderr, timeout=60):
         self.username = username
         self.password = password
@@ -123,84 +131,93 @@ class Session:
         self.counter = 0
         self.logged = self.login()
 
-    # Write in logfile if verbose enabled
     def write_log(self, text):
+        """ write text in the log file """
         if self.verbose:
-            self.logfile.write('[%s]: %s\n' % (time.strftime('%Y-%m-%d %H:%M:%S'), text))
+            self.logfile.write("[%s]: %s\n" % (time.strftime("%Y-%m-%d %H:%M:%S"), text))
 
-    # retrieve FamilySearch session ID (https://familysearch.org/developers/docs/guides/oauth2)
     def login(self):
+        """ retrieve FamilySearch session ID
+            (https://familysearch.org/developers/docs/guides/oauth2)
+        """
         while True:
             try:
-                url = 'https://www.familysearch.org/auth/familysearch/login'
-                self.write_log('Downloading: ' + url)
-                r = requests.get(url, params={'ldsauth': False}, allow_redirects=False)
-                url = r.headers['Location']
-                self.write_log('Downloading: ' + url)
+                url = "https://www.familysearch.org/auth/familysearch/login"
+                self.write_log("Downloading: " + url)
+                r = requests.get(url, params={"ldsauth": False}, allow_redirects=False)
+                url = r.headers["Location"]
+                self.write_log("Downloading: " + url)
                 r = requests.get(url, allow_redirects=False)
                 idx = r.text.index('name="params" value="')
-                span = r.text[idx + 21:].index('"')
-                params = r.text[idx + 21:idx + 21 + span]
-
-                url = 'https://ident.familysearch.org/cis-web/oauth2/v3/authorization'
-                self.write_log('Downloading: ' + url)
-                r = requests.post(url, data={'params': params, 'userName': self.username, 'password': self.password}, allow_redirects=False)
-
-                if 'The username or password was incorrect' in r.text:
-                    self.write_log('The username or password was incorrect')
+                span = r.text[idx + 21 :].index('"')
+                params = r.text[idx + 21 : idx + 21 + span]
+
+                url = "https://ident.familysearch.org/cis-web/oauth2/v3/authorization"
+                self.write_log("Downloading: " + url)
+                r = requests.post(
+                    url,
+                    data={"params": params, "userName": self.username, "password": self.password},
+                    allow_redirects=False,
+                )
+
+                if "The username or password was incorrect" in r.text:
+                    self.write_log("The username or password was incorrect")
                     return False
 
-                if 'Invalid Oauth2 Request' in r.text:
-                    self.write_log('Invalid Oauth2 Request')
+                if "Invalid Oauth2 Request" in r.text:
+                    self.write_log("Invalid Oauth2 Request")
                     time.sleep(self.timeout)
                     continue
 
-                url = r.headers['Location']
-                self.write_log('Downloading: ' + url)
+                url = r.headers["Location"]
+                self.write_log("Downloading: " + url)
                 r = requests.get(url, allow_redirects=False)
-                self.fssessionid = r.cookies['fssessionid']
+                self.fssessionid = r.cookies["fssessionid"]
             except requests.exceptions.ReadTimeout:
-                self.write_log('Read timed out')
+                self.write_log("Read timed out")
                 continue
             except requests.exceptions.ConnectionError:
-                self.write_log('Connection aborted')
+                self.write_log("Connection aborted")
                 time.sleep(self.timeout)
                 continue
             except requests.exceptions.HTTPError:
-                self.write_log('HTTPError')
+                self.write_log("HTTPError")
                 time.sleep(self.timeout)
                 continue
             except KeyError:
-                self.write_log('KeyError')
+                self.write_log("KeyError")
                 time.sleep(self.timeout)
                 continue
             except ValueError:
-                self.write_log('ValueError')
+                self.write_log("ValueError")
                 time.sleep(self.timeout)
                 continue
-            self.write_log('FamilySearch session id: ' + self.fssessionid)
+            self.write_log("FamilySearch session id: " + self.fssessionid)
             return True
 
-    # retrieve JSON structure from FamilySearch URL
     def get_url(self, url):
+        """ retrieve JSON structure from a FamilySearch URL """
         self.counter += 1
         while True:
             try:
-                self.write_log('Downloading: ' + url)
-                # r = requests.get(url, cookies = { 's_vi': self.s_vi, 'fssessionid' : self.fssessionid }, timeout = self.timeout)
-                r = requests.get('https://familysearch.org' + url, cookies={'fssessionid': self.fssessionid}, timeout=self.timeout)
+                self.write_log("Downloading: " + url)
+                r = requests.get(
+                    "https://familysearch.org" + url,
+                    cookies={"fssessionid": self.fssessionid},
+                    timeout=self.timeout,
+                )
             except requests.exceptions.ReadTimeout:
-                self.write_log('Read timed out')
+                self.write_log("Read timed out")
                 continue
             except requests.exceptions.ConnectionError:
-                self.write_log('Connection aborted')
+                self.write_log("Connection aborted")
                 time.sleep(self.timeout)
                 continue
-            self.write_log('Status code: ' + str(r.status_code))
+            self.write_log("Status code: %s" % r.status_code)
             if r.status_code == 204:
                 return None
             if r.status_code in {404, 405, 410, 500}:
-                self.write_log('WARNING: ' + url)
+                self.write_log("WARNING: " + url)
                 return None
             if r.status_code == 401:
                 self.login()
@@ -208,38 +225,49 @@ class Session:
             try:
                 r.raise_for_status()
             except requests.exceptions.HTTPError:
-                self.write_log('HTTPError')
+                self.write_log("HTTPError")
                 if r.status_code == 403:
-                    if 'message' in r.json()['errors'][0] and r.json()['errors'][0]['message'] == u'Unable to get ordinances.':
-                        self.write_log('Unable to get ordinances. Try with an LDS account or without option -c.')
-                        return 'error'
-                    else:
-
-                        self.write_log('WARNING: code 403 from %s %s' % (url, r.json()['errors'][0]['message'] or ''))
-                        return None
+                    if (
+                        "message" in r.json()["errors"][0]
+                        and r.json()["errors"][0]["message"] == "Unable to get ordinances."
+                    ):
+                        self.write_log(
+                            "Unable to get ordinances. "
+                            "Try with an LDS account or without option -c."
+                        )
+                        return "error"
+                    self.write_log(
+                        "WARNING: code 403 from %s %s"
+                        % (url, r.json()["errors"][0]["message"] or "")
+                    )
+                    return None
                 time.sleep(self.timeout)
                 continue
             try:
                 return r.json()
             except Exception as e:
-                self.write_log('WARNING: corrupted file from %s, error: %s' % (url, e))
+                self.write_log("WARNING: corrupted file from %s, error: %s" % (url, e))
                 return None
 
-    # retrieve FamilySearch current user ID
     def set_current(self):
-        url = '/platform/users/current.json'
+        """ retrieve FamilySearch current user ID, name and language """
+        url = "/platform/users/current.json"
         data = self.get_url(url)
         if data:
-            self.fid = data['users'][0]['personId']
-            self.lang = data['users'][0]['preferredLanguage']
-            self.display_name = data['users'][0]['displayName']
+            self.fid = data["users"][0]["personId"]
+            self.lang = data["users"][0]["preferredLanguage"]
+            self.display_name = data["users"][0]["displayName"]
 
     def get_userid(self):
+        """ get FamilySearch current user ID """
         if not self.fid:
             self.set_current()
         return self.fid
 
     def _(self, string):
+        """ translate a string into user's language
+            TODO replace translation file for gettext format
+        """
         if not self.lang:
             self.set_current()
         if string in translations and self.lang in translations[string]:
@@ -247,12 +275,16 @@ class Session:
         return string
 
 
-# some GEDCOM objects
 class Note:
+    """ GEDCOM Note class
+        :param text: the Note content
+        :param tree: a Tree object
+        :param num: the GEDCOM identifier
+    """
 
     counter = 0
 
-    def __init__(self, text='', tree=None, num=None):
+    def __init__(self, text="", tree=None, num=None):
         if num:
             self.num = num
         else:
@@ -264,13 +296,20 @@ class Note:
             tree.notes.append(self)
 
     def print(self, file=sys.stdout):
-        file.write(cont('0 @N' + str(self.num) + '@ NOTE ' + self.text) + '\n')
+        """ print Note in GEDCOM format """
+        file.write(cont("0 @N%s@ NOTE %s" % (self.num, self.text)))
 
     def link(self, file=sys.stdout, level=1):
-        file.write(str(level) + ' NOTE @N' + str(self.num) + '@\n')
+        """ print the reference in GEDCOM format """
+        file.write("%s NOTE @N%s@\n" % (level, self.num))
 
 
 class Source:
+    """ GEDCOM Source class
+        :param data: FS Source data
+        :param tree: a Tree object
+        :param num: the GEDCOM identifier
+    """
 
     counter = 0
 
@@ -285,167 +324,198 @@ class Source:
         self.url = self.citation = self.title = self.fid = None
         self.notes = set()
         if data:
-            self.fid = data['id']
-            if 'about' in data:
-                self.url = data['about'].replace('familysearch.org/platform/memories/memories', 'www.familysearch.org/photos/artifacts')
-            if 'citations' in data:
-                self.citation = data['citations'][0]['value']
-            if 'titles' in data:
-                self.title = data['titles'][0]['value']
-            if 'notes' in data:
-                for n in data['notes']:
-                    if n['text']:
-                        self.notes.add(Note(n['text'], self.tree))
+            self.fid = data["id"]
+            if "about" in data:
+                self.url = data["about"].replace(
+                    "familysearch.org/platform/memories/memories",
+                    "www.familysearch.org/photos/artifacts",
+                )
+            if "citations" in data:
+                self.citation = data["citations"][0]["value"]
+            if "titles" in data:
+                self.title = data["titles"][0]["value"]
+            if "notes" in data:
+                for n in data["notes"]:
+                    if n["text"]:
+                        self.notes.add(Note(n["text"], self.tree))
 
     def print(self, file=sys.stdout):
-        file.write('0 @S' + str(self.num) + '@ SOUR \n')
+        """ print Source in GEDCOM format """
+        file.write("0 @S%s@ SOUR \n" % self.num)
         if self.title:
-            file.write(cont('1 TITL ' + self.title) + '\n')
+            file.write(cont("1 TITL " + self.title))
         if self.citation:
-            file.write(cont('1 AUTH ' + self.citation) + '\n')
+            file.write(cont("1 AUTH " + self.citation))
         if self.url:
-            file.write(cont('1 PUBL ' + self.url) + '\n')
+            file.write(cont("1 PUBL " + self.url))
         for n in self.notes:
             n.link(file, 1)
-        file.write('1 REFN ' + self.fid + '\n')
+        file.write("1 REFN %s\n" % self.fid)
 
     def link(self, file=sys.stdout, level=1):
-        file.write(str(level) + ' SOUR @S' + str(self.num) + '@\n')
+        """ print the reference in GEDCOM format """
+        file.write("%s SOUR @S%s@\n" % (level, self.num))
 
 
 class Fact:
+    """ GEDCOM Fact class
+        :param data: FS Fact data
+        :param tree: a tree object
+    """
 
     def __init__(self, data=None, tree=None):
         self.value = self.type = self.date = self.place = self.note = self.map = None
         if data:
-            if 'value' in data:
-                self.value = data['value']
-            if 'type' in data:
-                self.type = data['type']
+            if "value" in data:
+                self.value = data["value"]
+            if "type" in data:
+                self.type = data["type"]
                 if self.type in FACT_EVEN:
                     self.type = tree.fs._(FACT_EVEN[self.type])
-                elif self.type[:6] == u'data:,':
+                elif self.type[:6] == "data:,":
                     self.type = self.type[6:]
                 elif self.type not in FACT_TAGS:
                     self.type = None
-            if 'date' in data:
-                self.date = data['date']['original']
-            if 'place' in data:
-                place = data['place']
-                self.place = place['original']
-                if 'description' in place and place['description'][1:] in tree.places:
-                    self.map = tree.places[place['description'][1:]]
-            if 'changeMessage' in data['attribution']:
-                self.note = Note(data['attribution']['changeMessage'], tree)
-            if self.type == 'http://gedcomx.org/Death' and not (self.date or self.place):
-                self.value = 'Y'
-
-    def print(self, file=sys.stdout, key=None):
+            if "date" in data:
+                self.date = data["date"]["original"]
+            if "place" in data:
+                place = data["place"]
+                self.place = place["original"]
+                if "description" in place and place["description"][1:] in tree.places:
+                    self.map = tree.places[place["description"][1:]]
+            if "changeMessage" in data["attribution"]:
+                self.note = Note(data["attribution"]["changeMessage"], tree)
+            if self.type == "http://gedcomx.org/Death" and not (self.date or self.place):
+                self.value = "Y"
+
+    def print(self, file=sys.stdout):
+        """ print Fact in GEDCOM format
+            the GEDCOM TAG depends on the type, defined in FACT_TAGS
+        """
         if self.type in FACT_TAGS:
-            tmp = '1 ' + FACT_TAGS[self.type]
+            tmp = "1 " + FACT_TAGS[self.type]
             if self.value:
-                tmp += ' ' + self.value
+                tmp += " " + self.value
             file.write(cont(tmp))
         elif self.type:
-            file.write('1 EVEN\n2 TYPE ' + self.type)
+            file.write("1 EVEN\n2 TYPE %s\n" % self.type)
             if self.value:
-                file.write('\n' + cont('2 NOTE Description: ' + self.value))
+                file.write(cont("2 NOTE Description: " + self.value))
         else:
             return
-        file.write('\n')
         if self.date:
-            file.write(cont('2 DATE ' + self.date) + '\n')
+            file.write(cont("2 DATE " + self.date))
         if self.place:
-            file.write(cont('2 PLAC ' + self.place) + '\n')
+            file.write(cont("2 PLAC " + self.place))
         if self.map:
             latitude, longitude = self.map
-            file.write('3 MAP\n4 LATI ' + latitude + '\n4 LONG ' + longitude + '\n')
+            file.write("3 MAP\n4 LATI %s\n4 LONG %s\n" % (latitude, longitude))
         if self.note:
             self.note.link(file, 2)
 
 
 class Memorie:
+    """ GEDCOM Memorie class
+        :param data: FS Memorie data
+    """
 
     def __init__(self, data=None):
         self.description = self.url = None
-        if data and 'links' in data:
-            self.url = data['about']
-            if 'titles' in data:
-                self.description = data['titles'][0]['value']
-            if 'descriptions' in data:
-                self.description = ('' if not self.description else self.description + '\n') + data['descriptions'][0]['value']
+        if data and "links" in data:
+            self.url = data["about"]
+            if "titles" in data:
+                self.description = data["titles"][0]["value"]
+            if "descriptions" in data:
+                self.description = ("" if not self.description else self.description + "\n") + data[
+                    "descriptions"
+                ][0]["value"]
 
     def print(self, file=sys.stdout):
-        file.write('1 OBJE\n2 FORM URL\n')
+        """ print Memorie in GEDCOM format """
+        file.write("1 OBJE\n2 FORM URL\n")
         if self.description:
-            file.write(cont('2 TITL ' + self.description) + '\n')
+            file.write(cont("2 TITL " + self.description))
         if self.url:
-            file.write(cont('2 FILE ' + self.url) + '\n')
+            file.write(cont("2 FILE " + self.url))
 
 
 class Name:
+    """ GEDCOM Name class
+        :param data: FS Name data
+        :param tree: a Tree object
+    """
 
     def __init__(self, data=None, tree=None):
-        self.given = ''
-        self.surname = ''
+        self.given = ""
+        self.surname = ""
         self.prefix = None
         self.suffix = None
         self.note = None
         if data:
-            if 'parts' in data['nameForms'][0]:
-                for z in data['nameForms'][0]['parts']:
-                    if z['type'] == u'http://gedcomx.org/Given':
-                        self.given = z['value']
-                    if z['type'] == u'http://gedcomx.org/Surname':
-                        self.surname = z['value']
-                    if z['type'] == u'http://gedcomx.org/Prefix':
-                        self.prefix = z['value']
-                    if z['type'] == u'http://gedcomx.org/Suffix':
-                        self.suffix = z['value']
-            if 'changeMessage' in data['attribution']:
-                self.note = Note(data['attribution']['changeMessage'], tree)
+            if "parts" in data["nameForms"][0]:
+                for z in data["nameForms"][0]["parts"]:
+                    if z["type"] == "http://gedcomx.org/Given":
+                        self.given = z["value"]
+                    if z["type"] == "http://gedcomx.org/Surname":
+                        self.surname = z["value"]
+                    if z["type"] == "http://gedcomx.org/Prefix":
+                        self.prefix = z["value"]
+                    if z["type"] == "http://gedcomx.org/Suffix":
+                        self.suffix = z["value"]
+            if "changeMessage" in data["attribution"]:
+                self.note = Note(data["attribution"]["changeMessage"], tree)
 
     def print(self, file=sys.stdout, typ=None):
-        tmp = '1 NAME ' + self.given + ' /' + self.surname + '/'
+        """ print Name in GEDCOM format
+            :param typ: type for additional names
+        """
+        tmp = "1 NAME %s /%s/" % (self.given, self.surname)
         if self.suffix:
-            tmp += ' ' + self.suffix
-        file.write(cont(tmp) + '\n')
+            tmp += " " + self.suffix
+        file.write(cont(tmp))
         if typ:
-            file.write('2 TYPE ' + typ + '\n')
+            file.write("2 TYPE %s\n" % typ)
         if self.prefix:
-            file.write('2 NPFX ' + self.prefix + '\n')
+            file.write("2 NPFX %s\n" % self.prefix)
         if self.note:
             self.note.link(file, 2)
 
 
 class Ordinance:
+    """ GEDCOM Ordinance class
+        :param data: FS Ordinance data
+    """
 
     def __init__(self, data=None):
         self.date = self.temple_code = self.status = self.famc = None
         if data:
-            if 'date' in data:
-                self.date = data['date']['formal']
-            if 'templeCode' in data:
-                self.temple_code = data['templeCode']
-            self.status = data['status']
+            if "date" in data:
+                self.date = data["date"]["formal"]
+            if "templeCode" in data:
+                self.temple_code = data["templeCode"]
+            self.status = data["status"]
 
     def print(self, file=sys.stdout):
+        """ print Ordinance in Gecom format """
         if self.date:
-            file.write(cont('2 DATE ' + self.date) + '\n')
+            file.write(cont("2 DATE " + self.date))
         if self.temple_code:
-            file.write('2 TEMP ' + self.temple_code + '\n')
+            file.write("2 TEMP %s\n" % self.temple_code)
         if self.status in ORDINANCES_STATUS:
-            file.write('2 STAT ' + ORDINANCES_STATUS[self.status] + '\n')
+            file.write("2 STAT %s\n" % ORDINANCES_STATUS[self.status])
         if self.famc:
-            file.write('2 FAMC @F' + str(self.famc.num) + '@\n')
+            file.write("2 FAMC @F%s@\n" % self.famc.num)
 
 
-# GEDCOM individual class
 class Indi:
+    """ GEDCOM individual class
+        :param fid' FamilySearch id
+        :param tree: a tree object
+        :param num: the GEDCOM identifier
+    """
 
     counter = 0
 
-    # initialize individual
     def __init__(self, fid=None, tree=None, num=None):
         if num:
             self.num = num
@@ -474,159 +544,180 @@ class Indi:
         self.memories = set()
 
     def add_data(self, data):
+        """ add FS individual data """
         if data:
-            if data['names']:
-                for x in data['names']:
-                    if x['preferred']:
+            if data["names"]:
+                for x in data["names"]:
+                    if x["preferred"]:
                         self.name = Name(x, self.tree)
                     else:
-                        if x['type'] == u'http://gedcomx.org/Nickname':
+                        if x["type"] == "http://gedcomx.org/Nickname":
                             self.nicknames.add(Name(x, self.tree))
-                        if x['type'] == u'http://gedcomx.org/BirthName':
+                        if x["type"] == "http://gedcomx.org/BirthName":
                             self.birthnames.add(Name(x, self.tree))
-                        if x['type'] == u'http://gedcomx.org/AlsoKnownAs':
+                        if x["type"] == "http://gedcomx.org/AlsoKnownAs":
                             self.aka.add(Name(x, self.tree))
-                        if x['type'] == u'http://gedcomx.org/MarriedName':
+                        if x["type"] == "http://gedcomx.org/MarriedName":
                             self.married.add(Name(x, self.tree))
-            if 'gender' in data:
-                if data['gender']['type'] == 'http://gedcomx.org/Male':
-                    self.gender = 'M'
-                elif data['gender']['type'] == 'http://gedcomx.org/Female':
-                    self.gender = 'F'
-                elif data['gender']['type'] == 'http://gedcomx.org/Unknown':
-                    self.gender = 'U'
-            if 'facts' in data:
-                for x in data['facts']:
-                    if x['type'] == u'http://familysearch.org/v1/LifeSketch':
-                        self.notes.add(Note('=== ' + self.tree.fs._('Life Sketch') + ' ===\n' + x['value'], self.tree))
+            if "gender" in data:
+                if data["gender"]["type"] == "http://gedcomx.org/Male":
+                    self.gender = "M"
+                elif data["gender"]["type"] == "http://gedcomx.org/Female":
+                    self.gender = "F"
+                elif data["gender"]["type"] == "http://gedcomx.org/Unknown":
+                    self.gender = "U"
+            if "facts" in data:
+                for x in data["facts"]:
+                    if x["type"] == "http://familysearch.org/v1/LifeSketch":
+                        self.notes.add(
+                            Note(
+                                "=== %s ===\n%s"
+                                % (self.tree.fs._("Life Sketch"), x.get("value", "")),
+                                self.tree,
+                            )
+                        )
                     else:
                         self.facts.add(Fact(x, self.tree))
-            if 'sources' in data:
-                sources = self.tree.fs.get_url('/platform/tree/persons/%s/sources.json' % self.fid)
+            if "sources" in data:
+                sources = self.tree.fs.get_url("/platform/tree/persons/%s/sources.json" % self.fid)
                 if sources:
                     quotes = dict()
-                    for quote in sources['persons'][0]['sources']:
-                        quotes[quote['descriptionId']] = quote['attribution']['changeMessage'] if 'changeMessage' in quote['attribution'] else None
-                    for source in sources['sourceDescriptions']:
-                        if source['id'] not in self.tree.sources:
-                            self.tree.sources[source['id']] = Source(source, self.tree)
-                        self.sources.add((self.tree.sources[source['id']], quotes[source['id']]))
-            if 'evidence' in data:
-                url = '/platform/tree/persons/%s/memories.json' % self.fid
+                    for quote in sources["persons"][0]["sources"]:
+                        quotes[quote["descriptionId"]] = (
+                            quote["attribution"]["changeMessage"]
+                            if "changeMessage" in quote["attribution"]
+                            else None
+                        )
+                    for source in sources["sourceDescriptions"]:
+                        if source["id"] not in self.tree.sources:
+                            self.tree.sources[source["id"]] = Source(source, self.tree)
+                        self.sources.add((self.tree.sources[source["id"]], quotes[source["id"]]))
+            if "evidence" in data:
+                url = "/platform/tree/persons/%s/memories.json" % self.fid
                 memorie = self.tree.fs.get_url(url)
-                if memorie and 'sourceDescriptions' in memorie:
-                    for x in memorie['sourceDescriptions']:
-                        if x['mediaType'] == 'text/plain':
-                            text = '\n'.join(val.get('value', '') for val in x.get('titles', []) + x.get('descriptions', []))
+                if memorie and "sourceDescriptions" in memorie:
+                    for x in memorie["sourceDescriptions"]:
+                        if x["mediaType"] == "text/plain":
+                            text = "\n".join(
+                                val.get("value", "")
+                                for val in x.get("titles", []) + x.get("descriptions", [])
+                            )
                             self.notes.add(Note(text, self.tree))
                         else:
                             self.memories.add(Memorie(x))
 
-    # add a fams to the individual
     def add_fams(self, fams):
+        """ add family fid (for spouse or parent)"""
         self.fams_fid.add(fams)
 
-    # add a famc to the individual
     def add_famc(self, famc):
+        """ add family fid (for child) """
         self.famc_fid.add(famc)
 
-    # retrieve individual notes
     def get_notes(self):
-        notes = self.tree.fs.get_url('/platform/tree/persons/%s/notes.json' % self.fid)
+        """ retrieve individual notes """
+        notes = self.tree.fs.get_url("/platform/tree/persons/%s/notes.json" % self.fid)
         if notes:
-            for n in notes['persons'][0]['notes']:
-                text_note = '=== ' + n['subject'] + ' ===\n' if 'subject' in n else ''
-                text_note += n['text'] + '\n' if 'text' in n else ''
+            for n in notes["persons"][0]["notes"]:
+                text_note = "=== %s ===\n" % n["subject"] if 'subject' in n else ""
+                text_note += n["text"] + "\n" if "text" in n else ""
                 self.notes.add(Note(text_note, self.tree))
 
-    # retrieve LDS ordinances
     def get_ordinances(self):
+        """ retrieve LDS ordinances
+            need a LDS account
+        """
         res = []
         famc = False
-        url = '/platform/tree/persons/%s/ordinances.json' % self.fid
-        data = self.tree.fs.get_url(url)['persons'][0]['ordinances']
+        url = "/platform/tree/persons/%s/ordinances.json" % self.fid
+        data = self.tree.fs.get_url(url)["persons"][0]["ordinances"]
         if data:
             for o in data:
-                if o['type'] == u'http://lds.org/Baptism':
+                if o["type"] == "http://lds.org/Baptism":
                     self.baptism = Ordinance(o)
-                elif o['type'] == u'http://lds.org/Confirmation':
+                elif o["type"] == "http://lds.org/Confirmation":
                     self.confirmation = Ordinance(o)
-                elif o['type'] == u'http://lds.org/Endowment':
+                elif o["type"] == "http://lds.org/Endowment":
                     self.endowment = Ordinance(o)
-                elif o['type'] == u'http://lds.org/SealingChildToParents':
+                elif o["type"] == "http://lds.org/SealingChildToParents":
                     self.sealing_child = Ordinance(o)
-                    if 'father' in o and 'mother' in o:
-                        famc = (o['father']['resourceId'],
-                                o['mother']['resourceId'])
-                elif o['type'] == u'http://lds.org/SealingToSpouse':
+                    if "father" in o and "mother" in o:
+                        famc = (o["father"]["resourceId"], o["mother"]["resourceId"])
+                elif o["type"] == "http://lds.org/SealingToSpouse":
                     res.append(o)
         return res, famc
 
-    # retrieve contributors
     def get_contributors(self):
+        """ retrieve contributors """
         temp = set()
-        data = self.tree.fs.get_url('/platform/tree/persons/%s/changes.json' % self.fid)
+        data = self.tree.fs.get_url("/platform/tree/persons/%s/changes.json" % self.fid)
         if data:
-            for entries in data['entries']:
-                for contributors in entries['contributors']:
-                    temp.add(contributors['name'])
+            for entries in data["entries"]:
+                for contributors in entries["contributors"]:
+                    temp.add(contributors["name"])
         if temp:
-            text = '=== ' + self.tree.fs._('Contributors') + ' ===\n' + '\n'.join(sorted(temp))
+            text = "=== %s ===\n%s" % (self.tree.fs._("Contributors"), "\n".join(sorted(temp)))
             for n in self.tree.notes:
                 if n.text == text:
                     self.notes.add(n)
                     return
             self.notes.add(Note(text, self.tree))
 
-    # print individual information in GEDCOM format
     def print(self, file=sys.stdout):
-        file.write('0 @I' + str(self.num) + '@ INDI\n')
+        """ print individual in GEDCOM format """
+        file.write("0 @I%s@ INDI\n" % self.num)
         if self.name:
             self.name.print(file)
         for o in self.nicknames:
-            file.write(cont('2 NICK ' + o.given + ' ' + o .surname) + '\n')
+            file.write(cont("2 NICK %s %s" % (o.given, o.surname)))
         for o in self.birthnames:
             o.print(file)
         for o in self.aka:
-            o.print(file, 'aka')
+            o.print(file, "aka")
         for o in self.married:
-            o.print(file, 'married')
+            o.print(file, "married")
         if self.gender:
-            file.write('1 SEX ' + self.gender + '\n')
+            file.write("1 SEX %s\n" % self.gender)
         for o in self.facts:
             o.print(file)
         for o in self.memories:
             o.print(file)
         if self.baptism:
-            file.write('1 BAPL\n')
+            file.write("1 BAPL\n")
             self.baptism.print(file)
         if self.confirmation:
-            file.write('1 CONL\n')
+            file.write("1 CONL\n")
             self.confirmation.print(file)
         if self.endowment:
-            file.write('1 ENDL\n')
+            file.write("1 ENDL\n")
             self.endowment.print(file)
         if self.sealing_child:
-            file.write('1 SLGC\n')
+            file.write("1 SLGC\n")
             self.sealing_child.print(file)
         for num in self.fams_num:
-            file.write('1 FAMS @F' + str(num) + '@\n')
+            file.write("1 FAMS @F%s@\n" % num)
+            file.write("1 FAMS @F%s@\n" % num)
         for num in self.famc_num:
-            file.write('1 FAMC @F' + str(num) + '@\n')
-        file.write('1 _FSFTID ' + self.fid + '\n')
+            file.write("1 FAMC @F%s@\n" % num)
+        file.write("1 _FSFTID %s\n" % self.fid)
         for o in self.notes:
             o.link(file)
         for source, quote in self.sources:
             source.link(file, 1)
             if quote:
-                file.write(cont('2 PAGE ' + quote) + '\n')
+                file.write(cont("2 PAGE " + quote))
 
 
-# GEDCOM family class
 class Fam:
+    """ GEDCOM family class
+        :param husb: husbant fid
+        :param wife: wife fid
+        :param tree: a Tree object
+        :param num: a GEDCOM identifier
+    """
+
     counter = 0
 
-    # initialize family
     def __init__(self, husb=None, wife=None, tree=None, num=None):
         if num:
             self.num = num
@@ -644,87 +735,105 @@ class Fam:
         self.notes = set()
         self.sources = set()
 
-    # add a child to the family
     def add_child(self, child):
+        """ add a child fid to the family """
         if child not in self.chil_fid:
             self.chil_fid.add(child)
 
-    # retrieve and add marriage information
     def add_marriage(self, fid):
+        """ retrieve and add marriage information
+            :param fid: the marriage fid
+        """
         if not self.fid:
             self.fid = fid
-            url = '/platform/tree/couple-relationships/%s.json' % self.fid
+            url = "/platform/tree/couple-relationships/%s.json" % self.fid
             data = self.tree.fs.get_url(url)
             if data:
-                if 'facts' in data['relationships'][0]:
-                    for x in data['relationships'][0]['facts']:
+                if "facts" in data["relationships"][0]:
+                    for x in data["relationships"][0]["facts"]:
                         self.facts.add(Fact(x, self.tree))
-                if 'sources' in data['relationships'][0]:
+                if "sources" in data["relationships"][0]:
                     quotes = dict()
-                    for x in data['relationships'][0]['sources']:
-                        quotes[x['descriptionId']] = x['attribution']['changeMessage'] if 'changeMessage' in x['attribution'] else None
+                    for x in data["relationships"][0]["sources"]:
+                        quotes[x["descriptionId"]] = (
+                            x["attribution"]["changeMessage"]
+                            if "changeMessage" in x["attribution"]
+                            else None
+                        )
                     new_sources = quotes.keys() - self.tree.sources.keys()
                     if new_sources:
-                        sources = self.tree.fs.get_url('/platform/tree/couple-relationships/%s/sources.json' % self.fid)
-                        for source in sources['sourceDescriptions']:
-                            if source['id'] in new_sources and source['id'] not in self.tree.sources:
-                                self.tree.sources[source['id']] = Source(source, self.tree)
+                        sources = self.tree.fs.get_url(
+                            "/platform/tree/couple-relationships/%s/sources.json" % self.fid
+                        )
+                        for source in sources["sourceDescriptions"]:
+                            if (
+                                source["id"] in new_sources
+                                and source["id"] not in self.tree.sources
+                            ):
+                                self.tree.sources[source["id"]] = Source(source, self.tree)
                     for source_fid in quotes:
                         self.sources.add((self.tree.sources[source_fid], quotes[source_fid]))
 
-    # retrieve marriage notes
     def get_notes(self):
+        """ retrieve marriage notes """
         if self.fid:
-            notes = self.tree.fs.get_url('/platform/tree/couple-relationships/%s/notes.json' % self.fid)
+            notes = self.tree.fs.get_url(
+                "/platform/tree/couple-relationships/%s/notes.json" % self.fid
+            )
             if notes:
-                for n in notes['relationships'][0]['notes']:
-                    text_note = '=== ' + n['subject'] + ' ===\n' if 'subject' in n else ''
-                    text_note += n['text'] + '\n' if 'text' in n else ''
+                for n in notes["relationships"][0]["notes"]:
+                    text_note = "=== %s ===\n" % n["subject"] if "subject" in n else ""
+                    text_note += n["text"] + "\n" if "text" in n else ""
                     self.notes.add(Note(text_note, self.tree))
 
-    # retrieve contributors
     def get_contributors(self):
+        """ retrieve contributors """
         if self.fid:
             temp = set()
-            data = self.tree.fs.get_url('/platform/tree/couple-relationships/%s/changes.json' % self.fid)
+            data = self.tree.fs.get_url(
+                "/platform/tree/couple-relationships/%s/changes.json" % self.fid
+            )
             if data:
-                for entries in data['entries']:
-                    for contributors in entries['contributors']:
-                        temp.add(contributors['name'])
+                for entries in data["entries"]:
+                    for contributors in entries["contributors"]:
+                        temp.add(contributors["name"])
             if temp:
-                text = '=== ' + self.tree.fs._('Contributors') + ' ===\n' + '\n'.join(sorted(temp))
+                text = "=== %s ===\n%s" % (self.tree.fs._("Contributors"), "\n".join(sorted(temp)))
                 for n in self.tree.notes:
                     if n.text == text:
                         self.notes.add(n)
                         return
                 self.notes.add(Note(text, self.tree))
 
-    # print family information in GEDCOM format
     def print(self, file=sys.stdout):
-        file.write('0 @F' + str(self.num) + '@ FAM\n')
+        """ print family information in GEDCOM format """
+        file.write("0 @F%s@ FAM\n" % self.num)
         if self.husb_num:
-            file.write('1 HUSB @I' + str(self.husb_num) + '@\n')
+            file.write("1 HUSB @I%s@\n" % self.husb_num)
         if self.wife_num:
-            file.write('1 WIFE @I' + str(self.wife_num) + '@\n')
+            file.write("1 WIFE @I%s@\n" % self.wife_num)
         for num in self.chil_num:
-            file.write('1 CHIL @I' + str(num) + '@\n')
+            file.write("1 CHIL @I%s@\n" % num)
         for o in self.facts:
             o.print(file)
         if self.sealing_spouse:
-            file.write('1 SLGS\n')
+            file.write("1 SLGS\n")
             self.sealing_spouse.print(file)
         if self.fid:
-            file.write('1 _FSFTID ' + self.fid + '\n')
+            file.write("1 _FSFTID %s\n" % self.fid)
         for o in self.notes:
             o.link(file)
         for source, quote in self.sources:
             source.link(file, 1)
             if quote:
-                file.write(cont('2 PAGE ' + quote) + '\n')
+                file.write(cont("2 PAGE " + quote))
 
 
-# family tree class
 class Tree:
+    """ family tree class
+        :param fs: a Session object
+    """
+
     def __init__(self, fs=None):
         self.fs = fs
         self.indi = dict()
@@ -733,58 +842,72 @@ class Tree:
         self.sources = dict()
         self.places = dict()
 
-    # add individuals to the family tree
     def add_indis(self, fids):
+        """ add individuals to the family tree
+            :param fids: an iterable of fid
+        """
+
         async def add_datas(loop, data):
             futures = set()
-            for person in data['persons']:
-                self.indi[person['id']] = Indi(person['id'], self)
-                futures.add(loop.run_in_executor(None, self.indi[person['id']].add_data, person))
+            for person in data["persons"]:
+                self.indi[person["id"]] = Indi(person["id"], self)
+                futures.add(loop.run_in_executor(None, self.indi[person["id"]].add_data, person))
             for future in futures:
                 await future
 
         new_fids = [fid for fid in fids if fid and fid not in self.indi]
         loop = asyncio.new_event_loop()
         asyncio.set_event_loop(loop)
-        # loop = asyncio.get_event_loop()
-        while len(new_fids):
-            data = self.fs.get_url('/platform/tree/persons.json?pids=' + ','.join(new_fids[:MAX_PERSONS]))
+        while new_fids:
+            data = self.fs.get_url(
+                "/platform/tree/persons.json?pids=" + ",".join(new_fids[:MAX_PERSONS])
+            )
             if data:
-                if 'places' in data:
-                    for place in data['places']:
-                        if place['id'] not in self.places:
-                            self.places[place['id']] = (str(place['latitude']), str(place['longitude']))
+                if "places" in data:
+                    for place in data["places"]:
+                        if place["id"] not in self.places:
+                            self.places[place["id"]] = (
+                                str(place["latitude"]),
+                                str(place["longitude"]),
+                            )
                 loop.run_until_complete(add_datas(loop, data))
-                if 'childAndParentsRelationships' in data:
-                    for rel in data['childAndParentsRelationships']:
-                        father = rel['father']['resourceId'] if 'father' in rel else None
-                        mother = rel['mother']['resourceId'] if 'mother' in rel else None
-                        child = rel['child']['resourceId'] if 'child' in rel else None
+                if "childAndParentsRelationships" in data:
+                    for rel in data["childAndParentsRelationships"]:
+                        father = rel["father"]["resourceId"] if "father" in rel else None
+                        mother = rel["mother"]["resourceId"] if "mother" in rel else None
+                        child = rel["child"]["resourceId"] if "child" in rel else None
                         if child in self.indi:
                             self.indi[child].parents.add((father, mother))
                         if father in self.indi:
                             self.indi[father].children.add((father, mother, child))
                         if mother in self.indi:
                             self.indi[mother].children.add((father, mother, child))
-                if 'relationships' in data:
-                    for rel in data['relationships']:
-                        if rel['type'] == u'http://gedcomx.org/Couple':
-                            person1 = rel['person1']['resourceId']
-                            person2 = rel['person2']['resourceId']
-                            relfid = rel['id']
+                if "relationships" in data:
+                    for rel in data["relationships"]:
+                        if rel["type"] == "http://gedcomx.org/Couple":
+                            person1 = rel["person1"]["resourceId"]
+                            person2 = rel["person2"]["resourceId"]
+                            relfid = rel["id"]
                             if person1 in self.indi:
                                 self.indi[person1].spouses.add((person1, person2, relfid))
                             if person2 in self.indi:
                                 self.indi[person2].spouses.add((person1, person2, relfid))
             new_fids = new_fids[MAX_PERSONS:]
 
-    # add family to the family tree
     def add_fam(self, father, mother):
-        if not (father, mother) in self.fam:
+        """ add a family to the family tree
+            :param father: the father fid or None
+            :param mother: the mother fid or None
+        """
+        if (father, mother) not in self.fam:
             self.fam[(father, mother)] = Fam(father, mother, self)
 
-    # add a children relationship (possibly incomplete) to the family tree
     def add_trio(self, father, mother, child):
+        """ add a children relationship to the family tree
+            :param father: the father fid or None
+            :param mother: the mother fid or None
+            :param child: the child fid or None
+        """
         if father in self.indi:
             self.indi[father].add_fams((father, mother))
         if mother in self.indi:
@@ -794,32 +917,46 @@ class Tree:
             self.add_fam(father, mother)
             self.fam[(father, mother)].add_child(child)
 
-    # add parents relationships
     def add_parents(self, fids):
+        """ add parents relationships
+           :param fids: a set of fids
+        """
         parents = set()
-        for fid in (fids & self.indi.keys()):
+        for fid in fids & self.indi.keys():
             for couple in self.indi[fid].parents:
                 parents |= set(couple)
         if parents:
             self.add_indis(parents)
-        for fid in (fids & self.indi.keys()):
+        for fid in fids & self.indi.keys():
             for father, mother in self.indi[fid].parents:
-                if mother in self.indi and father in self.indi or not father and mother in self.indi or not mother and father in self.indi:
+                if (
+                    mother in self.indi
+                    and father in self.indi
+                    or not father
+                    and mother in self.indi
+                    or not mother
+                    and father in self.indi
+                ):
                     self.add_trio(father, mother, fid)
         return set(filter(None, parents))
 
-    # add spouse relationships
     def add_spouses(self, fids):
+        """ add spouse relationships
+            :param fids: a set of fid
+        """
+
         async def add(loop, rels):
             futures = set()
             for father, mother, relfid in rels:
                 if (father, mother) in self.fam:
-                    futures.add(loop.run_in_executor(None, self.fam[(father, mother)].add_marriage, relfid))
+                    futures.add(
+                        loop.run_in_executor(None, self.fam[(father, mother)].add_marriage, relfid)
+                    )
             for future in futures:
                 await future
 
         rels = set()
-        for fid in (fids & self.indi.keys()):
+        for fid in fids & self.indi.keys():
             rels |= self.indi[fid].spouses
         loop = asyncio.get_event_loop()
         if rels:
@@ -831,59 +968,75 @@ class Tree:
                     self.add_fam(father, mother)
             loop.run_until_complete(add(loop, rels))
 
-    # add children relationships
     def add_children(self, fids):
+        """ add children relationships
+            :param fids: a set of fid
+        """
         rels = set()
-        for fid in (fids & self.indi.keys()):
+        for fid in fids & self.indi.keys():
             rels |= self.indi[fid].children if fid in self.indi else set()
         children = set()
         if rels:
             self.add_indis(set.union(*(set(rel) for rel in rels)))
             for father, mother, child in rels:
-                if child in self.indi and (mother in self.indi and father in self.indi or not father and mother in self.indi or not mother and father in self.indi):
+                if child in self.indi and (
+                    mother in self.indi
+                    and father in self.indi
+                    or not father
+                    and mother in self.indi
+                    or not mother
+                    and father in self.indi
+                ):
                     self.add_trio(father, mother, child)
                     children.add(child)
         return children
 
-    # retrieve ordinances
     def add_ordinances(self, fid):
+        """ retrieve ordinances
+            :param fid: an individual fid
+        """
         if fid in self.indi:
             ret, famc = self.indi[fid].get_ordinances()
             if famc and famc in self.fam:
                 self.indi[fid].sealing_child.famc = self.fam[famc]
             for o in ret:
-                if (fid, o['spouse']['resourceId']) in self.fam:
-                    self.fam[(fid, o['spouse']['resourceId'])
-                             ].sealing_spouse = Ordinance(o)
-                elif (o['spouse']['resourceId'], fid) in self.fam:
-                    self.fam[(o['spouse']['resourceId'], fid)
-                             ].sealing_spouse = Ordinance(o)
+                if (fid, o["spouse"]["resourceId"]) in self.fam:
+                    self.fam[(fid, o["spouse"]["resourceId"])].sealing_spouse = Ordinance(o)
+                elif (o["spouse"]["resourceId"], fid) in self.fam:
+                    self.fam[(o["spouse"]["resourceId"], fid)].sealing_spouse = Ordinance(o)
 
     def reset_num(self):
+        """ reset all GEDCOM identifiers """
         for husb, wife in self.fam:
             self.fam[(husb, wife)].husb_num = self.indi[husb].num if husb else None
             self.fam[(husb, wife)].wife_num = self.indi[wife].num if wife else None
-            self.fam[(husb, wife)].chil_num = set([self.indi[chil].num for chil in self.fam[(husb, wife)].chil_fid])
+            self.fam[(husb, wife)].chil_num = set(
+                self.indi[chil].num for chil in self.fam[(husb, wife)].chil_fid
+            )
         for fid in self.indi:
-            self.indi[fid].famc_num = set([self.fam[(husb, wife)].num for husb, wife in self.indi[fid].famc_fid])
-            self.indi[fid].fams_num = set([self.fam[(husb, wife)].num for husb, wife in self.indi[fid].fams_fid])
+            self.indi[fid].famc_num = set(
+                self.fam[(husb, wife)].num for husb, wife in self.indi[fid].famc_fid
+            )
+            self.indi[fid].fams_num = set(
+                self.fam[(husb, wife)].num for husb, wife in self.indi[fid].fams_fid
+            )
 
-    # print GEDCOM file
     def print(self, file=sys.stdout):
-        file.write('0 HEAD\n')
-        file.write('1 CHAR UTF-8\n')
-        file.write('1 GEDC\n')
-        file.write('2 VERS 5.5.1\n')
-        file.write('2 FORM LINEAGE-LINKED\n')
-        file.write('1 SOUR getmyancestors\n')
-        file.write('2 VERS 1.0\n')
-        file.write('2 NAME getmyancestors\n')
-        file.write('1 DATE ' + time.strftime('%d %b %Y') + '\n')
-        file.write('2 TIME ' + time.strftime('%H:%M:%S') + '\n')
-        file.write('1 SUBM @SUBM@\n')
-        file.write('0 @SUBM@ SUBM\n')
-        file.write('1 NAME ' + self.fs.display_name + '\n')
-        file.write('1 LANG ' + babelfish.Language.fromalpha2(self.fs.lang).name + '\n')
+        """ print family tree in GEDCOM format """
+        file.write("0 HEAD\n")
+        file.write("1 CHAR UTF-8\n")
+        file.write("1 GEDC\n")
+        file.write("2 VERS 5.5.1\n")
+        file.write("2 FORM LINEAGE-LINKED\n")
+        file.write("1 SOUR getmyancestors\n")
+        file.write("2 VERS 1.0\n")
+        file.write("2 NAME getmyancestors\n")
+        file.write("1 DATE %s\n" % time.strftime("%d %b %Y"))
+        file.write("2 TIME %s\n" % time.strftime("%H:%M:%S"))
+        file.write("1 SUBM @SUBM@\n")
+        file.write("0 @SUBM@ SUBM\n")
+        file.write("1 NAME %s\n" % self.fs.display_name)
+        file.write("1 LANG %s\n" % babelfish.Language.fromalpha2(self.fs.lang).name)
 
         for fid in sorted(self.indi, key=lambda x: self.indi.__getitem__(x).num):
             self.indi[fid].print(file)
@@ -898,28 +1051,70 @@ class Tree:
                 if n.num == notes[i - 1].num:
                     continue
             n.print(file)
-        file.write('0 TRLR\n')
-
-
-if __name__ == '__main__':
-    parser = argparse.ArgumentParser(description='Retrieve GEDCOM data from FamilySearch Tree (4 Jul 2016)', add_help=False, usage='getmyancestors.py -u username -p password [options]')
-    parser.add_argument('-u', metavar='<STR>', type=str, help='FamilySearch username')
-    parser.add_argument('-p', metavar='<STR>', type=str, help='FamilySearch password')
-    parser.add_argument('-i', metavar='<STR>', nargs='+', type=str, help='List of individual FamilySearch IDs for whom to retrieve ancestors')
-    parser.add_argument('-a', metavar='<INT>', type=int, default=4, help='Number of generations to ascend [4]')
-    parser.add_argument('-d', metavar='<INT>', type=int, default=0, help='Number of generations to descend [0]')
-    parser.add_argument('-m', action="store_true", default=False, help='Add spouses and couples information [False]')
-    parser.add_argument('-r', action="store_true", default=False, help='Add list of contributors in notes [False]')
-    parser.add_argument('-c', action="store_true", default=False, help='Add LDS ordinances (need LDS account) [False]')
-    parser.add_argument("-v", action="store_true", default=False, help="Increase output verbosity [False]")
-    parser.add_argument('-t', metavar='<INT>', type=int, default=60, help='Timeout in seconds [60]')
-    parser.add_argument('--show-password', action="store_true", default=False, help="Show password in .settings file [False]")
+        file.write("0 TRLR\n")
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(
+        description="Retrieve GEDCOM data from FamilySearch Tree (4 Jul 2016)",
+        add_help=False,
+        usage="getmyancestors.py -u username -p password [options]",
+    )
+    parser.add_argument("-u", metavar="<STR>", type=str, help="FamilySearch username")
+    parser.add_argument("-p", metavar="<STR>", type=str, help="FamilySearch password")
+    parser.add_argument(
+        "-i",
+        metavar="<STR>",
+        nargs="+",
+        type=str,
+        help="List of individual FamilySearch IDs for whom to retrieve ancestors",
+    )
+    parser.add_argument(
+        "-a", metavar="<INT>", type=int, default=4, help="Number of generations to ascend [4]"
+    )
+    parser.add_argument(
+        "-d", metavar="<INT>", type=int, default=0, help="Number of generations to descend [0]"
+    )
+    parser.add_argument(
+        "-m", action="store_true", default=False, help="Add spouses and couples information [False]"
+    )
+    parser.add_argument(
+        "-r", action="store_true", default=False, help="Add list of contributors in notes [False]"
+    )
+    parser.add_argument(
+        "-c",
+        action="store_true",
+        default=False,
+        help="Add LDS ordinances (need LDS account) [False]",
+    )
+    parser.add_argument(
+        "-v", action="store_true", default=False, help="Increase output verbosity [False]"
+    )
+    parser.add_argument("-t", metavar="<INT>", type=int, default=60, help="Timeout in seconds [60]")
+    parser.add_argument(
+        "--show-password",
+        action="store_true",
+        default=False,
+        help="Show password in .settings file [False]",
+    )
     try:
-        parser.add_argument('-o', metavar='<FILE>', type=argparse.FileType('w', encoding='UTF-8'), default=sys.stdout, help='output GEDCOM file [stdout]')
-        parser.add_argument('-l', metavar='<FILE>', type=argparse.FileType('w', encoding='UTF-8'), default=sys.stderr, help='output log file [stderr]')
+        parser.add_argument(
+            "-o",
+            metavar="<FILE>",
+            type=argparse.FileType("w", encoding="UTF-8"),
+            default=sys.stdout,
+            help="output GEDCOM file [stdout]",
+        )
+        parser.add_argument(
+            "-l",
+            metavar="<FILE>",
+            type=argparse.FileType("w", encoding="UTF-8"),
+            default=sys.stderr,
+            help="output log file [stderr]",
+        )
     except TypeError:
-        sys.stderr.write('Python >= 3.4 is required to run this script\n')
-        sys.stderr.write('(see https://docs.python.org/3/whatsnew/3.4.html#argparse)\n')
+        sys.stderr.write("Python >= 3.4 is required to run this script\n")
+        sys.stderr.write("(see https://docs.python.org/3/whatsnew/3.4.html#argparse)\n")
         exit(2)
 
     # extract arguments from the command line
@@ -932,8 +1127,8 @@ if __name__ == '__main__':
 
     if args.i:
         for fid in args.i:
-            if not re.match(r'[A-Z0-9]{4}-[A-Z0-9]{3}', fid):
-                exit('Invalid FamilySearch ID: ' + fid)
+            if not re.match(r"[A-Z0-9]{4}-[A-Z0-9]{3}", fid):
+                exit("Invalid FamilySearch ID: " + fid)
 
     username = args.u if args.u else input("Enter FamilySearch username: ")
     password = args.p if args.p else getpass.getpass("Enter FamilySearch password: ")
@@ -943,35 +1138,36 @@ if __name__ == '__main__':
     # Report settings used when getmyancestors.py is executed.
 
     setting_list = [
-        string for setting in [
+        string
+        for setting in [
             [
-                str('-' + action.dest + ' ' + action.help),
+                str("-" + action.dest + " " + action.help),
                 username
-                    if action.dest is 'u'
+                if action.dest is "u"
                 else password
-                    if action.dest is 'p' and args.show_password
-                else '******'
-                    if action.dest is 'p'
+                if action.dest is "p" and args.show_password
+                else "******"
+                if action.dest is "p"
                 else str(vars(args)[action.dest].name)
-                    if hasattr(vars(args)[action.dest], 'name')
-                else str(vars(args)[action.dest])]
-            for action in vars(parser)['_actions']] for string in setting]
-    setting_list.insert(0, time.strftime('%X %x %Z'))
-    setting_list.insert(0, 'time stamp: ')
-
-    formatting = '{:74}{:\t>1}\n' * int(len(setting_list) / 2)
-    settings_output = ((
-                formatting
-                ).format(
-                    *setting_list))
-
-    if not (args.o.name == '<stdout>'):
-        with open(
-            args.o.name.split('.')[0] + '.settings', 'w') as settings_record:
-                settings_record.write(settings_output)
+                if hasattr(vars(args)[action.dest], "name")
+                else str(vars(args)[action.dest]),
+            ]
+            for action in vars(parser)["_actions"]
+        ]
+        for string in setting
+    ]
+    setting_list.insert(0, time.strftime("%X %x %Z"))
+    setting_list.insert(0, "time stamp: ")
+
+    formatting = "{:74}{:\t>1}\n" * int(len(setting_list) / 2)
+    settings_output = (formatting).format(*setting_list)
+
+    if args.o.name != "<stdout>":
+        with open(args.o.name.split(".")[0] + ".settings", "w") as settings_record:
+            settings_record.write(settings_output)
 
     # initialize a FamilySearch session and a family tree object
-    print('Login to FamilySearch...')
+    print("Login to FamilySearch...")
     fs = Session(username, password, args.v, args.l, args.t)
     if not fs.logged:
         exit(2)
@@ -979,12 +1175,15 @@ if __name__ == '__main__':
     tree = Tree(fs)
 
     # check LDS account
-    if args.c and fs.get_url('/platform/tree/persons/%s/ordinances.json' % fs.get_userid()) == 'error':
+    if (
+        args.c
+        and fs.get_url("/platform/tree/persons/%s/ordinances.json" % fs.get_userid()) == "error"
+    ):
         exit(2)
 
     # add list of starting individuals to the family tree
     todo = args.i if args.i else [fs.get_userid()]
-    print(_('Downloading starting individuals...'))
+    print(_("Downloading starting individuals..."))
     tree.add_indis(todo)
 
     # download ancestors
@@ -994,7 +1193,7 @@ if __name__ == '__main__':
         if not todo:
             break
         done |= todo
-        print(_('Downloading %s. of generations of ancestors...') % (i + 1))
+        print(_("Downloading %s. of generations of ancestors...") % (i + 1))
         todo = tree.add_parents(todo) - done
 
     # download descendants
@@ -1004,12 +1203,12 @@ if __name__ == '__main__':
         if not todo:
             break
         done |= todo
-        print(_('Downloading %s. of generations of descendants...') % (i + 1))
+        print(_("Downloading %s. of generations of descendants...") % (i + 1))
         todo = tree.add_children(todo) - done
 
     # download spouses
     if args.m:
-        print(_('Downloading spouses and marriage information...'))
+        print(_("Downloading spouses and marriage information..."))
         todo = set(tree.indi.keys())
         tree.add_spouses(todo)
 
@@ -1030,10 +1229,28 @@ if __name__ == '__main__':
             await future
 
     loop = asyncio.get_event_loop()
-    print(_('Downloading notes') + (((',' if args.r else _(' and')) + _(' ordinances')) if args.c else '') + (_(' and contributors') if args.r else '') + '...')
+    print(
+        _("Downloading notes")
+        + ((("," if args.r else _(" and")) + _(" ordinances")) if args.c else "")
+        + (_(" and contributors") if args.r else "")
+        + "..."
+    )
     loop.run_until_complete(download_stuff(loop))
 
     # compute number for family relationships and print GEDCOM file
     tree.reset_num()
     tree.print(args.o)
-    print(_('Downloaded %s individuals, %s families, %s sources and %s notes in %s seconds with %s HTTP requests.') % (str(len(tree.indi)), str(len(tree.fam)), str(len(tree.sources)), str(len(tree.notes)), str(round(time.time() - time_count)), str(fs.counter)))
+    print(
+        _(
+            "Downloaded %s individuals, %s families, %s sources and %s notes "
+            "in %s seconds with %s HTTP requests."
+        )
+        % (
+            str(len(tree.indi)),
+            str(len(tree.fam)),
+            str(len(tree.sources)),
+            str(len(tree.notes)),
+            str(round(time.time() - time_count)),
+            str(fs.counter),
+        )
+    )
index 8af9c4efa7d4d8c27ffb833f669df5a981bf69b9..99809d4752180151199cc1f971e22d1da67471b0 100755 (executable)
@@ -1,5 +1,5 @@
 #!/usr/bin/env python3
-# -*- coding: utf-8 -*-
+# coding: utf-8
 """
    mergemyancestors.py - Merge GEDCOM data from FamilySearch Tree
    Copyright (C) 2014-2016 Giulio Genovese (giulio.genovese@gmail.com)
@@ -29,7 +29,7 @@ import sys
 import argparse
 
 # local import
-from getmyancestors import *
+import getmyancestors as gt
 
 sys.path.append(os.path.dirname(sys.argv[0]))
 
@@ -38,11 +38,12 @@ def reversed_dict(d):
     return {val: key for key, val in d.items()}
 
 
-FACT_TYPES = reversed_dict(FACT_TAGS)
-ORDINANCES = reversed_dict(ORDINANCES_STATUS)
+FACT_TYPES = reversed_dict(gt.FACT_TAGS)
+ORDINANCES = reversed_dict(gt.ORDINANCES_STATUS)
 
 
 class Gedcom:
+    """ Parse a GEDCOM file into a Tree """
 
     def __init__(self, file, tree):
         self.f = file
@@ -61,31 +62,34 @@ class Gedcom:
         self.__add_id()
 
     def __parse(self):
+        """ Parse the GEDCOM file into self.tree """
         while self.__get_line():
-            if self.tag == 'INDI':
-                self.num = int(self.pointer[2:len(self.pointer) - 1])
-                self.indi[self.num] = Indi(tree=self.tree, num=self.num)
+            if self.tag == "INDI":
+                self.num = int(self.pointer[2 : len(self.pointer) - 1])
+                self.indi[self.num] = gt.Indi(tree=self.tree, num=self.num)
                 self.__get_indi()
-            elif self.tag == 'FAM':
-                self.num = int(self.pointer[2:len(self.pointer) - 1])
+            elif self.tag == "FAM":
+                self.num = int(self.pointer[2 : len(self.pointer) - 1])
                 if self.num not in self.fam:
-                    self.fam[self.num] = Fam(tree=self.tree, num=self.num)
+                    self.fam[self.num] = gt.Fam(tree=self.tree, num=self.num)
                 self.__get_fam()
-            elif self.tag == 'NOTE':
-                self.num = int(self.pointer[2:len(self.pointer) - 1])
+            elif self.tag == "NOTE":
+                self.num = int(self.pointer[2 : len(self.pointer) - 1])
                 if self.num not in self.note:
-                    self.note[self.num] = Note(tree=self.tree, num=self.num)
+                    self.note[self.num] = gt.Note(tree=self.tree, num=self.num)
                 self.__get_note()
-            elif self.tag == 'SOUR':
-                self.num = int(self.pointer[2:len(self.pointer) - 1])
+            elif self.tag == "SOUR":
+                self.num = int(self.pointer[2 : len(self.pointer) - 1])
                 if self.num not in self.sour:
-                    self.sour[self.num] = Source(num=self.num)
+                    self.sour[self.num] = gt.Source(num=self.num)
                 self.__get_source()
             else:
                 continue
 
     def __get_line(self):
-        # if the flag is set, skip reading a newline
+        """ Parse a new line
+            If the flag is set, skip reading a newline
+        """
         if self.flag:
             self.flag = False
             return True
@@ -94,75 +98,78 @@ class Gedcom:
         if not words:
             return False
         self.level = int(words[0])
-        if words[1][0] == '@':
+        if words[1][0] == "@":
             self.pointer = words[1]
             self.tag = words[2]
-            self.data = ' '.join(words[3:])
+            self.data = " ".join(words[3:])
         else:
             self.pointer = None
             self.tag = words[1]
-            self.data = ' '.join(words[2:])
+            self.data = " ".join(words[2:])
         return True
 
     def __get_indi(self):
+        """ Parse an individual """
         while self.f and self.__get_line() and self.level > 0:
-            if self.tag == 'NAME':
+            if self.tag == "NAME":
                 self.__get_name()
-            elif self.tag == 'SEX':
+            elif self.tag == "SEX":
                 self.indi[self.num].gender = self.data
-            elif self.tag in FACT_TYPES or self.tag == 'EVEN':
+            elif self.tag in FACT_TYPES or self.tag == "EVEN":
                 self.indi[self.num].facts.add(self.__get_fact())
-            elif self.tag == 'BAPL':
+            elif self.tag == "BAPL":
                 self.indi[self.num].baptism = self.__get_ordinance()
-            elif self.tag == 'CONL':
+            elif self.tag == "CONL":
                 self.indi[self.num].confirmation = self.__get_ordinance()
-            elif self.tag == 'ENDL':
+            elif self.tag == "ENDL":
                 self.indi[self.num].endowment = self.__get_ordinance()
-            elif self.tag == 'SLGC':
+            elif self.tag == "SLGC":
                 self.indi[self.num].sealing_child = self.__get_ordinance()
-            elif self.tag == 'FAMS':
-                self.indi[self.num].fams_num.add(int(self.data[2:len(self.data) - 1]))
-            elif self.tag == 'FAMC':
-                self.indi[self.num].famc_num.add(int(self.data[2:len(self.data) - 1]))
-            elif self.tag == '_FSFTID':
+            elif self.tag == "FAMS":
+                self.indi[self.num].fams_num.add(int(self.data[2 : len(self.data) - 1]))
+            elif self.tag == "FAMC":
+                self.indi[self.num].famc_num.add(int(self.data[2 : len(self.data) - 1]))
+            elif self.tag == "_FSFTID":
                 self.indi[self.num].fid = self.data
-            elif self.tag == 'NOTE':
-                num = int(self.data[2:len(self.data) - 1])
+            elif self.tag == "NOTE":
+                num = int(self.data[2 : len(self.data) - 1])
                 if num not in self.note:
-                    self.note[num] = Note(tree=self.tree, num=num)
+                    self.note[num] = gt.Note(tree=self.tree, num=num)
                 self.indi[self.num].notes.add(self.note[num])
-            elif self.tag == 'SOUR':
+            elif self.tag == "SOUR":
                 self.indi[self.num].sources.add(self.__get_link_source())
-            elif self.tag == 'OBJE':
+            elif self.tag == "OBJE":
                 self.indi[self.num].memories.add(self.__get_memorie())
         self.flag = True
 
     def __get_fam(self):
+        """ Parse a family """
         while self.__get_line() and self.level > 0:
-            if self.tag == 'HUSB':
-                self.fam[self.num].husb_num = int(self.data[2:len(self.data) - 1])
-            elif self.tag == 'WIFE':
-                self.fam[self.num].wife_num = int(self.data[2:len(self.data) - 1])
-            elif self.tag == 'CHIL':
-                self.fam[self.num].chil_num.add(int(self.data[2:len(self.data) - 1]))
+            if self.tag == "HUSB":
+                self.fam[self.num].husb_num = int(self.data[2 : len(self.data) - 1])
+            elif self.tag == "WIFE":
+                self.fam[self.num].wife_num = int(self.data[2 : len(self.data) - 1])
+            elif self.tag == "CHIL":
+                self.fam[self.num].chil_num.add(int(self.data[2 : len(self.data) - 1]))
             elif self.tag in FACT_TYPES:
                 self.fam[self.num].facts.add(self.__get_fact())
-            elif self.tag == 'SLGS':
+            elif self.tag == "SLGS":
                 self.fam[self.num].sealing_spouse = self.__get_ordinance()
-            elif self.tag == '_FSFTID':
+            elif self.tag == "_FSFTID":
                 self.fam[self.num].fid = self.data
-            elif self.tag == 'NOTE':
-                num = int(self.data[2:len(self.data) - 1])
+            elif self.tag == "NOTE":
+                num = int(self.data[2 : len(self.data) - 1])
                 if num not in self.note:
-                    self.note[num] = Note(tree=self.tree, num=num)
+                    self.note[num] = gt.Note(tree=self.tree, num=num)
                 self.fam[self.num].notes.add(self.note[num])
-            elif self.tag == 'SOUR':
+            elif self.tag == "SOUR":
                 self.fam[self.num].sources.add(self.__get_link_source())
         self.flag = True
 
     def __get_name(self):
-        parts = self.__get_text().split('/')
-        name = Name()
+        """ Parse a name """
+        parts = self.__get_text().split("/")
+        name = gt.Name()
         added = False
         name.given = parts[0].strip()
         name.surname = parts[1].strip()
@@ -172,74 +179,77 @@ class Gedcom:
             self.indi[self.num].name = name
             added = True
         while self.__get_line() and self.level > 1:
-            if self.tag == 'NPFX':
+            if self.tag == "NPFX":
                 name.prefix = self.data
-            elif self.tag == 'TYPE':
-                if self.data == 'aka':
+            elif self.tag == "TYPE":
+                if self.data == "aka":
                     self.indi[self.num].aka.add(name)
                     added = True
-                elif self.data == 'married':
+                elif self.data == "married":
                     self.indi[self.num].married.add(name)
                     added = True
-            elif self.tag == 'NICK':
-                nick = Name()
+            elif self.tag == "NICK":
+                nick = gt.Name()
                 nick.given = self.data
                 self.indi[self.num].nicknames.add(nick)
-            elif self.tag == 'NOTE':
-                num = int(self.data[2:len(self.data) - 1])
+            elif self.tag == "NOTE":
+                num = int(self.data[2 : len(self.data) - 1])
                 if num not in self.note:
-                    self.note[num] = Note(tree=self.tree, num=num)
+                    self.note[num] = gt.Note(tree=self.tree, num=num)
                 name.note = self.note[num]
         if not added:
             self.indi[self.num].birthnames.add(name)
         self.flag = True
 
     def __get_fact(self):
-        fact = Fact()
-        if self.tag != 'EVEN':
+        """ Parse a fact """
+        fact = gt.Fact()
+        if self.tag != "EVEN":
             fact.type = FACT_TYPES[self.tag]
             fact.value = self.data
         while self.__get_line() and self.level > 1:
-            if self.tag == 'TYPE':
+            if self.tag == "TYPE":
                 fact.type = self.data
-            if self.tag == 'DATE':
+            if self.tag == "DATE":
                 fact.date = self.__get_text()
-            elif self.tag == 'PLAC':
+            elif self.tag == "PLAC":
                 fact.place = self.__get_text()
-            elif self.tag == 'MAP':
+            elif self.tag == "MAP":
                 fact.map = self.__get_map()
-            elif self.tag == 'NOTE':
-                if self.data[:12] == 'Description:':
+            elif self.tag == "NOTE":
+                if self.data[:12] == "Description:":
                     fact.value = self.data[13:]
                     continue
-                num = int(self.data[2:len(self.data) - 1])
+                num = int(self.data[2 : len(self.data) - 1])
                 if num not in self.note:
-                    self.note[num] = Note(tree=self.tree, num=num)
+                    self.note[num] = gt.Note(tree=self.tree, num=num)
                 fact.note = self.note[num]
-            elif self.tag == 'CONT':
-                fact.value += '\n' + self.data
-            elif self.tag == 'CONC':
+            elif self.tag == "CONT":
+                fact.value += "\n" + self.data
+            elif self.tag == "CONC":
                 fact.value += self.data
         self.flag = True
         return fact
 
     def __get_map(self):
+        """ Parse map coordinates """
         latitude = None
         longitude = None
         while self.__get_line() and self.level > 3:
-            if self.tag == 'LATI':
+            if self.tag == "LATI":
                 latitude = self.data
-            elif self.tag == 'LONG':
+            elif self.tag == "LONG":
                 longitude = self.data
         self.flag = True
         return (latitude, longitude)
 
     def __get_text(self):
+        """ Parse a multiline text """
         text = self.data
         while self.__get_line():
-            if self.tag == 'CONT':
-                text += '\n' + self.data
-            elif self.tag == 'CONC':
+            if self.tag == "CONT":
+                text += "\n" + self.data
+            elif self.tag == "CONC":
                 text += self.data
             else:
                 break
@@ -247,69 +257,75 @@ class Gedcom:
         return text
 
     def __get_source(self):
+        """ Parse a source """
         while self.__get_line() and self.level > 0:
-            if self.tag == 'TITL':
+            if self.tag == "TITL":
                 self.sour[self.num].title = self.__get_text()
-            elif self.tag == 'AUTH':
+            elif self.tag == "AUTH":
                 self.sour[self.num].citation = self.__get_text()
-            elif self.tag == 'PUBL':
+            elif self.tag == "PUBL":
                 self.sour[self.num].url = self.__get_text()
-            elif self.tag == 'REFN':
+            elif self.tag == "REFN":
                 self.sour[self.num].fid = self.data
                 if self.data in self.tree.sources:
                     self.sour[self.num] = self.tree.sources[self.data]
                 else:
                     self.tree.sources[self.data] = self.sour[self.num]
-            elif self.tag == 'NOTE':
-                num = int(self.data[2:len(self.data) - 1])
+            elif self.tag == "NOTE":
+                num = int(self.data[2 : len(self.data) - 1])
                 if num not in self.note:
-                    self.note[num] = Note(tree=self.tree, num=num)
+                    self.note[num] = gt.Note(tree=self.tree, num=num)
                 self.sour[self.num].notes.add(self.note[num])
         self.flag = True
 
     def __get_link_source(self):
-        num = int(self.data[2:len(self.data) - 1])
+        """ Parse a link to a source """
+        num = int(self.data[2 : len(self.data) - 1])
         if num not in self.sour:
-            self.sour[num] = Source(num=num)
+            self.sour[num] = gt.Source(num=num)
         page = None
         while self.__get_line() and self.level > 1:
-            if self.tag == 'PAGE':
+            if self.tag == "PAGE":
                 page = self.__get_text()
         self.flag = True
         return (self.sour[num], page)
 
     def __get_memorie(self):
-        memorie = Memorie()
+        """ Parse a memorie """
+        memorie = gt.Memorie()
         while self.__get_line() and self.level > 1:
-            if self.tag == 'TITL':
+            if self.tag == "TITL":
                 memorie.description = self.__get_text()
-            elif self.tag == 'FILE':
+            elif self.tag == "FILE":
                 memorie.url = self.__get_text()
         self.flag = True
         return memorie
 
     def __get_note(self):
+        """ Parse a note """
         self.note[self.num].text = self.__get_text()
         self.flag = True
 
     def __get_ordinance(self):
-        ordinance = Ordinance()
+        """ Parse an ordinance """
+        ordinance = gt.Ordinance()
         while self.__get_line() and self.level > 1:
-            if self.tag == 'DATE':
+            if self.tag == "DATE":
                 ordinance.date = self.__get_text()
-            elif self.tag == 'TEMP':
+            elif self.tag == "TEMP":
                 ordinance.temple_code = self.data
-            elif self.tag == 'STAT':
+            elif self.tag == "STAT":
                 ordinance.status = ORDINANCES[self.data]
-            elif self.tag == 'FAMC':
-                num = int(self.data[2:len(self.data) - 1])
+            elif self.tag == "FAMC":
+                num = int(self.data[2 : len(self.data) - 1])
                 if num not in self.fam:
-                    self.fam[num] = Fam(tree=self.tree, num=num)
+                    self.fam[num] = gt.Fam(tree=self.tree, num=num)
                 ordinance.famc = self.fam[num]
         self.flag = True
         return ordinance
 
     def __add_id(self):
+        """ Reset GEDCOM identifiers """
         for num in self.fam:
             if self.fam[num].husb_num:
                 self.fam[num].husb_fid = self.indi[self.fam[num].husb_num].fid
@@ -324,14 +340,32 @@ class Gedcom:
                 self.indi[num].fams_fid.add((self.fam[fams].husb_fid, self.fam[fams].wife_fid))
 
 
-if __name__ == '__main__':
-    parser = argparse.ArgumentParser(description='Merge GEDCOM data from FamilySearch Tree (4 Jul 2016)', add_help=False, usage='mergemyancestors.py -i input1.ged input2.ged ... [options]')
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(
+        description="Merge GEDCOM data from FamilySearch Tree (4 Jul 2016)",
+        add_help=False,
+        usage="mergemyancestors.py -i input1.ged input2.ged ... [options]",
+    )
     try:
-        parser.add_argument('-i', metavar='<FILE>', nargs='+', type=argparse.FileType('r', encoding='UTF-8'), default=sys.stdin, help='input GEDCOM files [stdin]')
-        parser.add_argument('-o', metavar='<FILE>', nargs='?', type=argparse.FileType('w', encoding='UTF-8'), default=sys.stdout, help='output GEDCOM files [stdout]')
+        parser.add_argument(
+            "-i",
+            metavar="<FILE>",
+            nargs="+",
+            type=argparse.FileType("r", encoding="UTF-8"),
+            default=sys.stdin,
+            help="input GEDCOM files [stdin]",
+        )
+        parser.add_argument(
+            "-o",
+            metavar="<FILE>",
+            nargs="?",
+            type=argparse.FileType("w", encoding="UTF-8"),
+            default=sys.stdout,
+            help="output GEDCOM files [stdout]",
+        )
     except TypeError:
-        sys.stderr.write('Python >= 3.4 is required to run this script\n')
-        sys.stderr.write('(see https://docs.python.org/3/whatsnew/3.4.html#argparse)\n')
+        sys.stderr.write("Python >= 3.4 is required to run this script\n")
+        sys.stderr.write("(see https://docs.python.org/3/whatsnew/3.4.html#argparse)\n")
         exit(2)
 
     # extract arguments from the command line
@@ -342,7 +376,7 @@ if __name__ == '__main__':
         parser.print_help()
         exit(2)
 
-    tree = Tree()
+    tree = gt.Tree()
 
     indi_counter = 0
     fam_counter = 0
@@ -356,7 +390,7 @@ if __name__ == '__main__':
             fid = ged.indi[num].fid
             if fid not in tree.indi:
                 indi_counter += 1
-                tree.indi[fid] = Indi(tree=tree, num=indi_counter)
+                tree.indi[fid] = gt.Indi(tree=tree, num=indi_counter)
                 tree.indi[fid].tree = tree
                 tree.indi[fid].fid = ged.indi[num].fid
             tree.indi[fid].fams_fid |= ged.indi[num].fams_fid
@@ -382,7 +416,7 @@ if __name__ == '__main__':
             husb, wife = (ged.fam[num].husb_fid, ged.fam[num].wife_fid)
             if (husb, wife) not in tree.fam:
                 fam_counter += 1
-                tree.fam[(husb, wife)] = Fam(husb, wife, tree, fam_counter)
+                tree.fam[(husb, wife)] = gt.Fam(husb, wife, tree, fam_counter)
                 tree.fam[(husb, wife)].tree = tree
             tree.fam[(husb, wife)].chil_fid |= ged.fam[num].chil_fid
             if ged.fam[num].fid:
index 8eead429659980ac45213a67ff7ec78c6f28b5eb..3fa7955d4efd2dc625b4e0f6ff04e273f1dfc358 100644 (file)
 #!/usr/bin/env python3
-# -*- coding: utf-8 -*-
+# coding: utf-8
 
 translations = {
-    'Life Sketch': {
-        'de': 'Kurzinfo zum Lebenslauf',
-        'eo': '[Ļîƒé Šķéţçĥ---- П國カ내]',
-        'es': 'Resumen de la vida de la persona',
-        'fr': 'Biographie succincte',
-        'it': 'Profilo',
-        'ja': '生涯の概要',
-        'ko': '인생 개요',
-        'pt': 'Resumo da Vida',
-        'ru': 'Краткое жизнеописание',
-        'zh': '生活簡述'
+    "Life Sketch": {
+        "de": "Kurzinfo zum Lebenslauf",
+        "eo": "[Ļîƒé Šķéţçĥ---- П國カ내]",
+        "es": "Resumen de la vida de la persona",
+        "fr": "Biographie succincte",
+        "it": "Profilo",
+        "ja": "生涯の概要",
+        "ko": "인생 개요",
+        "pt": "Resumo da Vida",
+        "ru": "Краткое жизнеописание",
+        "zh": "生活簡述",
+    },
+    "Contributors": {
+        "de": "Mitwirkende",
+        "eo": "Contributors",
+        "es": "Colaboradores",
+        "fr": "Contributeurs",
+        "it": "Contributori",
+        "ja": "貢献者",
+        "ko": "기여자",
+        "pt": "Colaboradores",
+        "ru": "Авторы",
+        "zh": "贡献者",
+    },
+    "Stillborn": {
+        "de": "Tot geboren",
+        "eo": "[Šţîļļƀöŕñ----------- П國カ내]",
+        "es": "Nacido muerto o mortinato",
+        "fr": "Mort-né(e)",
+        "it": "Nato morto",
+        "ja": "死産",
+        "ko": "사산아",
+        "pt": "Natimorto",
+        "ru": "Мертворожденный",
+        "zh": "死胎",
+    },
+    "Affiliation": {
+        "de": "Zugehörigkeit",
+        "eo": "[Ńƒîļîåţîöñ---- П國カ내]",
+        "es": "Afiliación",
+        "fr": "Affiliation",
+        "it": "Affiliazione",
+        "ja": "所属",
+        "ko": "소속",
+        "pt": "Afiliação",
+        "ru": "Принадлежность",
+        "zh": "所屬團體",
+    },
+    "Clan Name": {
+        "de": "Bezeichnung des Clans",
+        "eo": "[Çļåñ Ñåɱé----------- П國カ내]",
+        "es": "Nombre del clan",
+        "fr": "Nom de clan",
+        "it": "Nome clan",
+        "ja": "氏族名",
+        "ko": "씨족 이름",
+        "pt": "Nome do Clã",
+        "ru": "Название клана",
+        "zh": "氏族名字",
+    },
+    "National Identification": {
+        "de": "Ausweisnummer",
+        "eo": "[Ñåţîöñåļ Îðéñţîƒîçåţîöñ----------- П國カ내]",
+        "es": "Identificación nacional",
+        "fr": "Numéro national d’identification",
+        "it": "Documento di identità",
+        "ja": "国民登録番号",
+        "ko": "주민등록번호",
+        "pt": "Carteira de Identidade Nacional",
+        "ru": "Удостоверение личности",
+        "zh": "國民身分證",
+    },
+    "Race": {
+        "de": "Ethnische Zugehörigkeit",
+        "eo": "[Ŕåçé- П國カ내]",
+        "es": "Raza",
+        "fr": "Race",
+        "it": "Razza",
+        "ja": "人種",
+        "ko": "인종",
+        "pt": "Raça",
+        "ru": "Раса",
+        "zh": "種族",
+    },
+    "Tribe Name": {
+        "de": "Bezeichnung des Stammes",
+        "eo": "[Ţŕîƀé Ñåɱé------------- П國カ내]",
+        "es": "Nombre de la tribu",
+        "fr": "Nom de tribu",
+        "it": "Nome tribù",
+        "ja": "部族名",
+        "ko": "부족명",
+        "pt": "Nome da Tribo",
+        "ru": "Название племени",
+        "zh": "部落名字",
+    },
+    "Downloading starting individuals...": {"fr": "Téléchargement des personnes de départ..."},
+    "Downloading %s. of generations of descendants...": {
+        "fr": "Téléchargement de %s génération(s) de descendants..."
+    },
+    "Downloading spouses and marriage information...": {
+        "fr": "Téléchargement des conjoints et des informations de mariage..."
+    },
+    "Downloading notes": {"fr": "Téléchargement des notes"},
+    " and": {"fr": " et"},
+    " ordinances": {"fr": " des ordonnances"},
+    " and contributors": {"fr": " et des contributeurs"},
+    "Downloaded %s individuals, %s families, %s sources and %s notes in %s seconds with %s HTTP requests.": {
+        "fr": "%s personnes, %s familles, %s sources et %s notes téléchargés en %s secondes avec %s requêtes HTTP."
+    },
+    "Download ": {"fr": "Téléchargement de la "},
+    "Copy": {"fr": "Copier"},
+    "Cut": {"fr": "Couper"},
+    "Paste": {"fr": "Coller"},
+    "Username:": {"fr": "Nom d'utilisateur :"},
+    "Password:": {"fr": "Mot de passe :"},
+    "ID already exist": {"fr": "Cet identifiant existe déjà"},
+    "Invalid FamilySearch ID: ": {"fr": "Identifiant FamilySearch invalide : "},
+    "Individual not found": {"fr": "Personne non trouvée"},
+    "Remove": {"fr": "Supprimer"},
+    "Number of generations to ascend": {"fr": "Nombre de générations d'ancêtres"},
+    "Number of generations to descend": {"fr": "Nombre de générations de descendants"},
+    "Add a FamilySearch ID": {"fr": "Ajouter un identifiant FamilySearch"},
+    "Add spouses and couples information": {
+        "fr": "Ajouter les conjoints et les informations de mariage"
+    },
+    "Add Temple information": {"fr": "Ajouter les informations du Temple"},
+    "Add list of contributors in notes": {
+        "fr": "Ajouter une liste des contributeurs dans les notes"
+    },
+    "Sign In to FamilySearch": {"fr": "Ouvrir une session FamilySearch"},
+    "Sign In": {"fr": "Ouvrir une session"},
+    "Quit": {"fr": "Quitter"},
+    "Save as": {"fr": "Enregistrer sous"},
+    "All files": {"fr": "Tous les fichiers"},
+    "Login to FamilySearch...": {"fr": "Connection à FamilySearch..."},
+    "The username or password was incorrect": {
+        "fr": "Le nom d'utilisateur ou le mot de passe est incorrect"
+    },
+    "Options": {"fr": "Options"},
+    "Downloading %s. of generations of ancestors...": {
+        "fr": "Téléchargement de %s génération(s) d'ancêtres..."
+    },
+    "Save": {"fr": "Sauvegarder"},
+    "Success ! Click below to save your GEDCOM file": {
+        "fr": "Succès ! Cliquez ci-dessous pour sauvegarder votre fichier GEDCOM"
+    },
+    "Download GEDCOM": {"fr": "Télécharger un GEDCOM"},
+    "Merge GEDCOMs": {"fr": "Fusionner des GEDCOM"},
+    "Merge": {"fr": "Fusionner"},
+    "Add files": {"fr": "Ajouter des fichiers"},
+    "File already exist: ": {"fr": "Ce fichier existe déjà: "},
+    "Open": {"fr": "Ouvrir"},
+    "File not found: ": {"fr": "Fichier non trouvé: "},
+    "Files successfully merged": {"fr": "Fichiers fusionnés avec succès"},
+    "Files": {"fr": "Fichiers"},
+    "Please add GEDCOM files": {"fr": "Veuillez ajouter des fichiers GEDCOM"},
+    "Error": {"fr": "Erreur"},
+    "Info": {"fr": "Info"},
+    "Name": {"fr": "Nom"},
+    "Warning: This tool should only be used to merge GEDCOM files from this software. If you use other GEDCOM files, the result is not guaranteed.": {
+        "fr": "Attention : Cet outil ne devrait être utilisé qu'avec des fichiers GEDCOM provenants de ce logiciel. Si vous utilisez d'autres fichiers GEDCOM, le résultat n'est pas garanti."
+    },
+    "Individuals: %s": {"fr": "Personnes : %s"},
+    "Families: %s": {"fr": "Familles : %s"},
+    "Sources: %s": {"fr": "Sources : %s"},
+    "Notes: %s": {"fr": "Notes : %s"},
+    "Elapsed time: %s:%s": {"fr": "Temps écoulé : %s:%s"},
+    "Please enter your FamilySearch username and password.": {
+        "fr": "Veuillez entrer votre nom d'utilisateur et votre mot de passe FamilySearch."
     },
-    'Contributors': {
-        'de': 'Mitwirkende',
-        'eo': 'Contributors',
-        'es': 'Colaboradores',
-        'fr': 'Contributeurs',
-        'it': 'Contributori',
-        'ja': '貢献者',
-        'ko': '기여자',
-        'pt': 'Colaboradores',
-        'ru': 'Авторы',
-        'zh': '贡献者'
-    },
-    'Stillborn': {
-        'de': 'Tot geboren',
-        'eo': '[Šţîļļƀöŕñ----------- П國カ내]',
-        'es': 'Nacido muerto o mortinato',
-        'fr': 'Mort-né(e)',
-        'it': 'Nato morto',
-        'ja': '死産',
-        'ko': '사산아',
-        'pt': 'Natimorto',
-        'ru': 'Мертворожденный',
-        'zh': '死胎'
-    },
-    'Affiliation': {
-        'de': 'Zugehörigkeit',
-        'eo': '[Ńƒîļîåţîöñ---- П國カ내]',
-        'es': 'Afiliación',
-        'fr': 'Affiliation',
-        'it': 'Affiliazione',
-        'ja': '所属',
-        'ko': '소속',
-        'pt': 'Afiliação',
-        'ru': 'Принадлежность',
-        'zh': '所屬團體'
-    },
-    'Clan Name': {
-        'de': 'Bezeichnung des Clans',
-        'eo': '[Çļåñ Ñåɱé----------- П國カ내]',
-        'es': 'Nombre del clan',
-        'fr': 'Nom de clan',
-        'it': 'Nome clan',
-        'ja': '氏族名',
-        'ko': '씨족 이름',
-        'pt': 'Nome do Clã',
-        'ru': 'Название клана',
-        'zh': '氏族名字'
-    },
-    'National Identification': {
-        'de': 'Ausweisnummer',
-        'eo': '[Ñåţîöñåļ Îðéñţîƒîçåţîöñ----------- П國カ내]',
-        'es': 'Identificación nacional',
-        'fr': 'Numéro national d’identification',
-        'it': 'Documento di identità',
-        'ja': '国民登録番号',
-        'ko': '주민등록번호',
-        'pt': 'Carteira de Identidade Nacional',
-        'ru': 'Удостоверение личности',
-        'zh': '國民身分證'
-    },
-    'Race': {
-        'de': 'Ethnische Zugehörigkeit',
-        'eo': '[Ŕåçé- П國カ내]',
-        'es': 'Raza',
-        'fr': 'Race',
-        'it': 'Razza',
-        'ja': '人種',
-        'ko': '인종',
-        'pt': 'Raça',
-        'ru': 'Раса',
-        'zh': '種族'
-    },
-    'Tribe Name': {
-        'de': 'Bezeichnung des Stammes',
-        'eo': '[Ţŕîƀé Ñåɱé------------- П國カ내]',
-        'es': 'Nombre de la tribu',
-        'fr': 'Nom de tribu',
-        'it': 'Nome tribù',
-        'ja': '部族名',
-        'ko': '부족명',
-        'pt': 'Nome da Tribo',
-        'ru': 'Название племени',
-        'zh': '部落名字'
-    },
-    'Downloading starting individuals...': {
-        'fr': 'Téléchargement des personnes de départ...',
-    },
-    'Downloading %s. of generations of descendants...': {
-        'fr': 'Téléchargement de %s génération(s) de descendants...',
-    },
-    'Downloading spouses and marriage information...': {
-        'fr': 'Téléchargement des conjoints et des informations de mariage...',
-    },
-    'Downloading notes': {
-        'fr': 'Téléchargement des notes',
-    },
-    ' and': {
-        'fr': ' et',
-    },
-    ' ordinances': {
-        'fr': ' des ordonnances',
-    },
-    ' and contributors': {
-        'fr': ' et des contributeurs',
-    },
-    'Downloaded %s individuals, %s families, %s sources and %s notes in %s seconds with %s HTTP requests.': {
-        'fr': '%s personnes, %s familles, %s sources et %s notes téléchargés en %s secondes avec %s requêtes HTTP.',
-    },
-    'Download ': {
-        'fr': 'Téléchargement de la ',
-    },
-    'Copy': {
-        'fr': 'Copier',
-    },
-    'Cut': {
-        'fr': 'Couper',
-    },
-    'Paste': {
-        'fr': 'Coller',
-    },
-    'Username:': {
-        'fr': "Nom d'utilisateur :",
-    },
-    'Password:': {
-        'fr': 'Mot de passe :',
-    },
-    'ID already exist': {
-        'fr': 'Cet identifiant existe déjà',
-    },
-    'Invalid FamilySearch ID: ': {
-        'fr': 'Identifiant FamilySearch invalide : ',
-    },
-    'Individual not found': {
-        'fr': 'Personne non trouvée',
-    },
-    'Remove': {
-        'fr': 'Supprimer',
-    },
-    'Number of generations to ascend': {
-        'fr': "Nombre de générations d'ancêtres",
-    },
-    'Number of generations to descend': {
-        'fr': 'Nombre de générations de descendants',
-    },
-    'Add a FamilySearch ID': {
-        'fr': 'Ajouter un identifiant FamilySearch',
-    },
-    'Add spouses and couples information': {
-        'fr': 'Ajouter les conjoints et les informations de mariage',
-    },
-    'Add Temple information': {
-        'fr': 'Ajouter les informations du Temple',
-    },
-    'Add list of contributors in notes': {
-        'fr': 'Ajouter une liste des contributeurs dans les notes',
-    },
-    'Sign In to FamilySearch': {
-        'fr': 'Ouvrir une session FamilySearch',
-    },
-    'Sign In': {
-        'fr': 'Ouvrir une session',
-    },
-    'Quit': {
-        'fr': 'Quitter',
-    },
-    'Save as': {
-        'fr': 'Enregistrer sous',
-    },
-    'All files': {
-        'fr': 'Tous les fichiers',
-    },
-    'Login to FamilySearch...': {
-        'fr': 'Connection à FamilySearch...',
-    },
-    'The username or password was incorrect': {
-        'fr': "Le nom d'utilisateur ou le mot de passe est incorrect",
-    },
-    'Options': {
-        'fr': 'Options',
-    },
-    'Downloading %s. of generations of ancestors...': {
-        'fr': "Téléchargement de %s génération(s) d'ancêtres...",
-    },
-    'Save': {
-        'fr': 'Sauvegarder',
-    },
-    'Success ! Click below to save your GEDCOM file': {
-        'fr': 'Succès ! Cliquez ci-dessous pour sauvegarder votre fichier GEDCOM',
-    },
-    'Download GEDCOM': {
-        'fr': 'Télécharger un GEDCOM',
-    },
-    'Merge GEDCOMs': {
-        'fr': 'Fusionner des GEDCOM',
-    },
-    'Merge': {
-        'fr': 'Fusionner',
-    },
-    'Add files': {
-        'fr': 'Ajouter des fichiers',
-    },
-    'File already exist: ': {
-        'fr': 'Ce fichier existe déjà: ',
-    },
-    'Open': {
-        'fr': 'Ouvrir',
-    },
-    'File not found: ': {
-        'fr': 'Fichier non trouvé: ',
-    },
-    'Files successfully merged': {
-        'fr': 'Fichiers fusionnés avec succès',
-    },
-    'Files': {
-        'fr': 'Fichiers',
-    },
-    'Please add GEDCOM files': {
-        'fr': 'Veuillez ajouter des fichiers GEDCOM',
-    },
-    'Error': {
-        'fr': 'Erreur',
-    },
-    'Info': {
-        'fr': 'Info',
-    },
-    'Name': {
-        'fr': 'Nom',
-    },
-    'Warning: This tool should only be used to merge GEDCOM files from this software. If you use other GEDCOM files, the result is not guaranteed.': {
-        'fr': "Attention : Cet outil ne devrait être utilisé qu'avec des fichiers GEDCOM provenants de ce logiciel. Si vous utilisez d'autres fichiers GEDCOM, le résultat n'est pas garanti.",
-    },
-    'Individuals: %s': {
-        'fr': 'Personnes : %s',
-    },
-    'Families: %s': {
-        'fr': 'Familles : %s',
-    },
-    'Sources: %s': {
-        'fr': 'Sources : %s',
-    },
-    'Notes: %s': {
-        'fr': 'Notes : %s',
-    },
-    'Elapsed time: %s:%s': {
-        'fr': 'Temps écoulé : %s:%s',
-    },
-    'Please enter your FamilySearch username and password.': {
-        'fr': "Veuillez entrer votre nom d'utilisateur et votre mot de passe FamilySearch."
-    }
 }