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/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..0a15d6f 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. @@ -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() @@ -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") @@ -224,21 +224,21 @@ 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): """ 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..0d5ce69 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. @@ -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 """ @@ -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 9a7fcb7..a0d60c8 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. @@ -24,7 +24,7 @@ # Global imports import string -from events import Event +from .events import Event class Line: """ Line of a GEDCOM file @@ -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. """ @@ -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): @@ -85,7 +87,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 +107,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 @@ -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() @@ -154,7 +156,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 = [] @@ -226,29 +228,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): @@ -265,9 +261,10 @@ 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): """ Return a person's names as a tuple: (first,last) """ first = "" @@ -277,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": @@ -304,8 +301,8 @@ 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 @@ -329,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 @@ -361,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 @@ -397,7 +394,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 """ @@ -407,7 +404,7 @@ def common_ancestor(self, relative): me = {} him = {} - + me['new'] = [self] me['old'] = [] him['new'] = [relative] @@ -444,7 +441,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 """ @@ -452,21 +450,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 @@ -505,7 +503,7 @@ def down_path(ancestor, descendant, distance = None): if ancestor.children() == []: return None - + if descendant in ancestor.children(): return [ancestor] @@ -517,7 +515,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): @@ -534,7 +532,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 @@ -570,13 +568,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 @@ -592,7 +590,7 @@ def _init(self): """ Implementing Line._init() Initialise husband, wife and children attributes. """ - + try: self._husband = self.children_tag_records("HUSB")[0] except IndexError: 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()