From 579519343e50063676245640adc15f219f9eaac7 Mon Sep 17 00:00:00 2001 From: Gabriel Burca Date: Fri, 7 Feb 2014 23:03:48 -0600 Subject: [PATCH 1/5] Added unit test for UTF-8 files with BOM - Added __init__.py to the test directory so that unittest discovery can detect the test scripts automatically. --- README.rst | 7 +++++++ test/__init__.py | 1 + test/test_bom.py | 21 +++++++++++++++++++++ 3 files changed, 29 insertions(+) create mode 100644 test/__init__.py create mode 100644 test/test_bom.py diff --git a/README.rst b/README.rst index 4b3d30b..05d0123 100644 --- a/README.rst +++ b/README.rst @@ -14,6 +14,13 @@ Documentation is in docs/. Examples of how to use this parser are at docs/examples. If you find documentation and/or examples confusing, let me know and I'll try to fix it. +Unit Tests +---------- + +Unit tests can be executed from the project root directory using: + + PYTHONPATH=simplepyged python -m unittest discover + Licence -------- diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..11d27f8 --- /dev/null +++ b/test/__init__.py @@ -0,0 +1 @@ +__version__ = '0.1' diff --git a/test/test_bom.py b/test/test_bom.py new file mode 100644 index 0000000..26de48e --- /dev/null +++ b/test/test_bom.py @@ -0,0 +1,21 @@ +import unittest +import os +from gedcom import * + + +class GedcomWithBomTest(unittest.TestCase): + """Unit tests for simplepyged using GedcomWithBOM.ged.""" + + def setUp(self): + self.g = Gedcom(os.path.abspath('test/GedcomWithBOM.ged')) + + def test_parser(self): + """Check if parser collected all records""" + self.assertEqual(len(self.g.record_dict()), 1) + + self.assertEqual(len(self.g.individual_list()), 1) + self.assertEqual(len(self.g.family_list()), 0) + + +if __name__ == '__main__': + unittest.main() From 23d2337d463344197634298092f9e51a05cede43 Mon Sep 17 00:00:00 2001 From: Gabriel Burca Date: Sat, 18 Feb 2017 21:23:06 -0600 Subject: [PATCH 2/5] Fix Individual.mutual_families() --- simplepyged/records.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/simplepyged/records.py b/simplepyged/records.py index 9a7fcb7..dd5b854 100644 --- a/simplepyged/records.py +++ b/simplepyged/records.py @@ -444,7 +444,8 @@ def mutual_families(self, candidate): for my_family in self.parent_families(): if my_family in candidate.parent_families(): mutual_families.append(my_family) - + + return mutual_families def is_parent(self, candidate): """ Determine if candidate is parent of self """ From 4379ae7a86b105b4ab50d57a4a80cc694836d226 Mon Sep 17 00:00:00 2001 From: Gabriel Burca Date: Sat, 30 Nov 2019 00:34:22 -0600 Subject: [PATCH 3/5] Return a list of mothers/fathers for consistency - If the return type changes based on the number of fathers/mothers, the caller doesn't know what to expect. --- simplepyged/records.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/simplepyged/records.py b/simplepyged/records.py index dd5b854..8f0a4a7 100644 --- a/simplepyged/records.py +++ b/simplepyged/records.py @@ -226,29 +226,23 @@ def families(self): return self._families def father(self): - """Returns a father as an Individual object. If person has multiple fathers, returns a list of Individual objects. """ + """Returns a list of fathers as Individual objects.""" fathers = [] for family in self.parent_families(): if family.husband() != None: fathers.append(family.husband()) - if len(fathers) == 1: - return fathers[0] - return fathers def mother(self): - """Returns a mother as an Individual object. If person has multiple mothers, returns a list of Individual objects. """ + """Returns a list of mothers as Individual objects.""" mothers = [] for family in self.parent_families(): if family.wife() != None: mothers.append(family.wife()) - if len(mothers) == 1: - return mothers[0] - return mothers def children(self): From da0cc14f996d235d3d6287c0644f1f0f5f6cc86a Mon Sep 17 00:00:00 2001 From: Gabriel Burca Date: Sat, 30 Nov 2019 00:36:34 -0600 Subject: [PATCH 4/5] Whitespace clean-up --- simplepyged/events.py | 4 ++-- simplepyged/gedcom.py | 8 ++++---- simplepyged/matches.py | 10 +++++----- simplepyged/records.py | 38 +++++++++++++++++++------------------- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/simplepyged/events.py b/simplepyged/events.py index 082a953..92e6d1a 100644 --- a/simplepyged/events.py +++ b/simplepyged/events.py @@ -13,7 +13,7 @@ # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. @@ -39,7 +39,7 @@ def _get_value(self, tag): return self.line.children_tags(tag)[0].value() except IndexError: return None - + def dateplace(self): """ Returns a pair of strings in format (date, place) """ date = '' diff --git a/simplepyged/gedcom.py b/simplepyged/gedcom.py index 35678bb..48662ec 100644 --- a/simplepyged/gedcom.py +++ b/simplepyged/gedcom.py @@ -15,7 +15,7 @@ # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. @@ -146,7 +146,7 @@ def _parse_line(self,number,line): t = self._tag(number,head) #retrieve line tag v = tail #retrieve value of tag if it exists - + # create the line if l > self._current_level + 1: self._error(number,"Structure of GEDCOM file is corrupted") @@ -235,10 +235,10 @@ def _print(self): class GedcomParseError(Exception): """ Exception raised when a Gedcom parsing error occurs """ - + def __init__(self, value): self.value = value - + def __str__(self): return self.value diff --git a/simplepyged/matches.py b/simplepyged/matches.py index 3944e07..b8a311e 100644 --- a/simplepyged/matches.py +++ b/simplepyged/matches.py @@ -15,7 +15,7 @@ # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. @@ -81,7 +81,7 @@ def criteria_match(self,criteria): * deathrange=[year1-year2] * marriage=[year] * marriagerange=[year1-year2] - + """ # error checking on the criteria @@ -145,7 +145,7 @@ def criteria_match(self,criteria): match = False except ValueError: match = False - + return match def marriage_year_match(self,year): @@ -178,7 +178,7 @@ class MatchList: gedcom = Gedcom(somefile) list = gedcom.individual_list() individual = gedcom.get_individual(xref) - + if MatchIndividual(individual).given_match(some_name): individual in MatchList(list).given_match(some_name) # this line returns True """ @@ -195,7 +195,7 @@ def __factory(self, method): def product(*args): return self.__abstract(method, *args) return product - + def __abstract(self, method, *args): retval = [] for record in self.records: diff --git a/simplepyged/records.py b/simplepyged/records.py index 8f0a4a7..12a9d7f 100644 --- a/simplepyged/records.py +++ b/simplepyged/records.py @@ -13,7 +13,7 @@ # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. @@ -47,7 +47,7 @@ class Line: the family record in which the associated individual is a spouse. Likewise, an line with a tag of FAMC has a value that points to a family record in which the associated individual is a child. - + See a Gedcom file for examples of tags and their values. """ @@ -85,7 +85,7 @@ def level(self): def xref(self): """ Return the xref of this line """ return self._xref - + def tag(self): """ Return the tag of this line """ return self._tag @@ -105,7 +105,7 @@ def parent_line(self): def add_child(self,line): """ Add a child line to this line """ self.children_lines().append(line) - + def add_parent_line(self,line): """ Add a parent line to this line """ self._parent_line = line @@ -154,7 +154,7 @@ class Record(Line): Child class of Line """ - + def _parse_generic_event_list(self, tag): """ Creates new event for each line with given tag""" retval = [] @@ -261,7 +261,7 @@ def get_families(self): def get_parent_families(self): """ Return a list of all of the family records in which this individual is a child. (adopted children can have multiple parent families)""" return self.children_tag_records("FAMC") - + def name(self): """ Return a person's names as a tuple: (first,last) """ first = "" @@ -299,7 +299,7 @@ def surname(self): def fathers_name(self): """ Return father's name (patronymic) """ return self.father().given_name() - + def birth(self): """ Return one randomly chosen birth event @@ -391,7 +391,7 @@ def parents(self): parent_pairs = map(lambda x: x.parents(), self.parent_families()) - return [parent for parent_pair in parent_pairs for parent in parent_pair] + return [parent for parent_pair in parent_pairs for parent in parent_pair] def common_ancestor(self, relative): """ Find a common ancestor with a relative """ @@ -401,7 +401,7 @@ def common_ancestor(self, relative): me = {} him = {} - + me['new'] = [self] me['old'] = [] him['new'] = [relative] @@ -447,21 +447,21 @@ def is_parent(self, candidate): return True return False - + def is_sibling(self, candidate): """ Determine if candidate is sibling of self """ if self.mutual_families(candidate): return True return False - + def is_relative(self, candidate): """ Determine if candidate is relative of self """ if self.common_ancestor(candidate) is not None: return True return False - + def distance_to_ancestor(self, ancestor): """Distance to an ancestor given in number of generations @@ -500,7 +500,7 @@ def down_path(ancestor, descendant, distance = None): if ancestor.children() == []: return None - + if descendant in ancestor.children(): return [ancestor] @@ -512,7 +512,7 @@ def down_path(ancestor, descendant, distance = None): if path is not None: path.insert(0, ancestor) return path - + return None def path_to_relative(self, relative): @@ -529,7 +529,7 @@ def path_to_relative(self, relative): if relative in self.parents(): return [[self, 'start'], [relative, 'parent']] - + common_ancestor = self.common_ancestor(relative) if common_ancestor is None: # is not relative @@ -565,13 +565,13 @@ def path_to_relative(self, relative): except IndexError: # sibling check didn't work out, we'll just # put common ancestor in there full_path.append([common_ancestor, 'child']) - + for step in his_path[1:]: #his path without common ancestor full_path.append([step, 'child']) full_path[-1][1] = '' # last person doesn't have next person to relate to - + return full_path - + class Family(Record): """ Gedcom record representing a family @@ -587,7 +587,7 @@ def _init(self): """ Implementing Line._init() Initialise husband, wife and children attributes. """ - + try: self._husband = self.children_tag_records("HUSB")[0] except IndexError: From c10e6ba1d5d0b993b2c3fcf52b24e290d7270a2b Mon Sep 17 00:00:00 2001 From: Gabriel Burca Date: Sat, 30 Nov 2019 01:09:30 -0600 Subject: [PATCH 5/5] Upgrade to python3 --- simplepyged/gedcom.py | 16 ++++++++-------- simplepyged/matches.py | 2 +- simplepyged/records.py | 25 ++++++++++++++----------- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/simplepyged/gedcom.py b/simplepyged/gedcom.py index 48662ec..0a15d6f 100644 --- a/simplepyged/gedcom.py +++ b/simplepyged/gedcom.py @@ -28,7 +28,7 @@ # Global imports import string -from records import * +from .records import * class Gedcom: """ Gedcom parser @@ -109,11 +109,11 @@ def get_family(self, xref): def _parse(self,file): # open file # go through the lines - f = open(file) number = 1 - for line in f.readlines(): - self._parse_line(number,line.decode("utf-8-sig")) - number += 1 + with open(file, encoding="utf-8-sig") as f: + for line in f.readlines(): + self._parse_line(number, line) + number += 1 for e in self.line_list(): e._init() @@ -224,12 +224,12 @@ def _tag(self,number,head): return head def _error(self,number,text): - error = "Gedcom format error on line " + unicode(number) + ': ' + text + error = "Gedcom format error on line " + str(number) + ': ' + text raise GedcomParseError(error) def _print(self): - for e in self.line_list: - print string.join([unicode(e.level()),e.xref(),e.tag(),e.value()]) + for e in self.line_list(): + print(" ".join([str(e.level()),e.xref(),e.tag(),e.value()])) class GedcomParseError(Exception): diff --git a/simplepyged/matches.py b/simplepyged/matches.py index b8a311e..0d5ce69 100644 --- a/simplepyged/matches.py +++ b/simplepyged/matches.py @@ -24,7 +24,7 @@ # # To contact the author, see http://github.com/dijxtra/simplepyged -from records import Individual +from .records import Individual class MatchIndividual(): """ Class for determining whether an Individual matches certain criteria """ diff --git a/simplepyged/records.py b/simplepyged/records.py index 12a9d7f..a0d60c8 100644 --- a/simplepyged/records.py +++ b/simplepyged/records.py @@ -24,7 +24,7 @@ # Global imports import string -from events import Event +from .events import Event class Line: """ Line of a GEDCOM file @@ -68,7 +68,9 @@ def __init__(self,level,xref,tag,value,dict): self._parent_line = None def _init(self): - """ A method which GEDCOM parser runs after all lines are available. Subclasses should implement this method if they want to work with other Lines at parse time, but after all Lines are parsed. """ + """ A method which GEDCOM parser runs after all lines are available. + Subclasses should implement this method if they want to work with other + Lines at parse time, but after all Lines are parsed.""" pass def type(self): @@ -132,14 +134,14 @@ def children_tag_records(self, tag): def gedcom(self): """ Return GEDCOM code for this line and all of its sub-lines """ - result = unicode(self) + result = str(self) for e in self.children_lines(): result += '\n' + e.gedcom() return result def __str__(self): """ Format this line as its original string """ - result = unicode(self.level()) + result = str(self.level()) if self.xref() != "": result += ' ' + self.xref() result += ' ' + self.tag() @@ -259,7 +261,8 @@ def get_families(self): return self.children_tag_records("FAMS") def get_parent_families(self): - """ Return a list of all of the family records in which this individual is a child. (adopted children can have multiple parent families)""" + """ Return a list of all of the family records in which this individual + is a child. (adopted children can have multiple parent families)""" return self.children_tag_records("FAMC") def name(self): @@ -271,9 +274,9 @@ def name(self): # some older Gedcom files don't use child tags but instead # place the name in the value of the NAME tag if e.value() != "": - name = string.split(e.value(),'/') - first = string.strip(name[0]) - last = string.strip(name[1]) if len(name) > 1 else None + name = e.value().split('/') + first = name[0].strip() + last = name[1].strip() if len(name) > 1 else None else: for c in e.children_lines(): if c.tag() == "GIVN": @@ -298,7 +301,7 @@ def surname(self): def fathers_name(self): """ Return father's name (patronymic) """ - return self.father().given_name() + return self.father()[0].given_name() def birth(self): """ Return one randomly chosen birth event @@ -323,7 +326,7 @@ def birth_year(self): return -1 try: - date = int(string.split(self.birth().date)[-1]) + date = int(self.birth().date.split()[-1]) return date except ValueError: return -1 @@ -355,7 +358,7 @@ def death_year(self): return -1 try: - date = int(string.split(self.death().date)[-1]) + date = int(self.death().date.split()[-1]) return date except ValueError: return -1