[geneagrapher] 14/226: Reorganizing for packaging (relates to ticket #11).

Doug Torrance dtorrance-guest at moszumanska.debian.org
Sat Jul 11 17:10:32 UTC 2015


This is an automated email from the git hooks/post-receive script.

dtorrance-guest pushed a commit to branch master
in repository geneagrapher.

commit 343dd3c101abd49923c4f0f72d3dfc28b10bf9dc
Author: David Alber <alber.david at gmail.com>
Date:   Fri May 9 04:03:22 2008 +0000

    Reorganizing for packaging (relates to ticket #11).
---
 geneagrapher/GGraph.py         | 256 +++++++++++++++++++++++
 geneagrapher/geneagraph-cgi.py | 119 +++++++++++
 geneagrapher/geneagrapher.py   | 149 ++++++++++++++
 geneagrapher/grab.py           |  79 +++++++
 geneagrapher/tests.py          | 454 +++++++++++++++++++++++++++++++++++++++++
 5 files changed, 1057 insertions(+)

diff --git a/geneagrapher/GGraph.py b/geneagrapher/GGraph.py
new file mode 100644
index 0000000..4e3ac6d
--- /dev/null
+++ b/geneagrapher/GGraph.py
@@ -0,0 +1,256 @@
+class DuplicateNodeError(Exception):
+    def __init__(self, value):
+        self.value = value
+        def __str__(self):
+            return repr(self.value)
+
+class Record:
+    """
+    Container class storing record of a mathematician in the graph.
+    """
+    def __init__(self, name, institution=None, year=None, id=None):
+        """
+        Record class constructor.
+        
+        Parameters:
+            name: string containing mathematician's name
+            institution: string containing mathematician's institution
+                (empty if none)
+            year: integer containing year degree was earned
+            id: integer containing Math Genealogy Project id value
+        """
+        self.name = name
+        self.institution = institution
+        self.year = year
+        self.id = id
+        
+        # Verify we got the types wanted.
+        if not isinstance(self.name, basestring):
+            raise TypeError("Unexpected parameter type: expected string value for 'name'")
+        if not isinstance(self.institution, basestring) and self.institution is not None:
+            raise TypeError("Unexpected parameter type: expected string value for 'institution'")
+        if not isinstance(self.year, int) and self.year is not None:
+            raise TypeError("Unexpected parameter type: expected integer value for 'year'")
+        if not isinstance(self.id, int) and self.id is not None:
+            raise TypeError("Unexpected parameter type: expected integer value for 'id'")
+
+    def __cmp__(self, r2):
+        """
+        Compare a pair of mathematician records based on ids.
+        """
+        return self.id.__cmp__(r2.id)
+    
+    def hasInstitution(self):
+        """
+        Return True if this record has an institution associated with it,
+        else False.
+        """
+        return self.institution is not None
+    
+    def hasYear(self):
+        """
+        Return True if this record has a year associated with it, else
+        False.
+        """
+        return self.year is not None
+    
+
+class Node:
+    """
+    Container class storing a node in the graph.
+    """
+    def __init__(self, record, ancestors, descendents):
+        """
+        Node class constructor.
+        
+        Parameters:
+            record: instance of the Record class
+            ancestors: list of the record's genealogical ancestors's
+                IDs
+            descendents: list of this records genealogical
+                descendent's IDs
+        """
+        
+        self.record = record
+        self.ancestors = ancestors
+        self.descendents = descendents
+        self.already_printed = False
+
+        # Verify parameter types.
+        if not isinstance(self.record, Record):
+            raise TypeError("Unexpected parameter type: expected Record object for 'record'")
+        if not isinstance(self.ancestors, list):
+            raise TypeError("Unexpected parameter type: expected list object for 'ancestors'")
+        if not isinstance(self.descendents, list):
+            raise TypeError("Unexpected parameter type: expected list object for 'descendents'")
+        
+    def __str__(self):
+        if self.record.hasInstitution():
+            if self.record.hasYear():
+                return self.record.name.encode('utf-8', 'replace') + ' \\n' + self.record.institution.encode('utf-8', 'replace') + ' (' + str(self.record.year) + ')'
+            else:
+                return self.record.name.encode('utf-8', 'replace') + ' \\n' + self.record.institution.encode('utf-8', 'replace')
+        else:
+            if self.record.hasYear():
+                return self.record.name.encode('utf-8', 'replace') + ' \\n(' + str(self.record.year) + ')'
+            else:
+                return self.record.name.encode('utf-8', 'replace')
+
+    def __cmp__(self, n2):
+        return self.record.__cmp__(n2.record)
+
+    def addAncestor(self, ancestor):
+        """
+        Append an ancestor id to the ancestor list.
+        """
+        # Verify we were passed an int.
+        if not isinstance(ancestor, int):
+            raise TypeError("Unexpected parameter type: expected int for 'ancestor'")
+        self.ancestors.append(ancestor)
+
+    def id(self):
+        """
+        Accessor method to retrieve the id of this node's record.
+        """
+        return self.record.id
+
+    def setId(self, id):
+        """
+        Sets the record id.
+        """
+        self.record.id = id
+
+
+class Graph:
+    """
+    Class storing the representation of a genealogy graph.
+    """
+    def __init__(self, heads=None):
+        """
+        Graph class constructor.
+        
+        Parameters:
+            heads: a list of Node objects representing the tree head
+                (can be omitted to create an empty graph)
+        """
+        self.heads = heads
+        self.supp_id = -1
+        
+        # Verify type of heads is what we expect.
+        if self.heads is not None:
+            if not isinstance(self.heads, list):
+                raise TypeError("Unexpected parameter type: expected list of Node objects for 'heads'")
+            for head in self.heads:
+                if not isinstance(head, Node):
+                    raise TypeError("Unexpected parameter type: expected list of Node objects for 'heads'")
+
+        self.nodes = {}
+        if self.heads is not None:
+            for head in self.heads:
+                self.nodes[head.id()] = head
+
+    def hasNode(self, id):
+        """
+        Check if the graph contains a node with the given id.
+        """
+        return self.nodes.has_key(id)
+
+    def getNode(self, id):
+        """
+        Return the node in the graph with given id. Returns
+        None if no such node exists.
+        """
+        if self.hasNode(id):
+            return self.nodes[id]
+        else:
+            return None
+
+    def getNodeList(self):
+        """
+        Return a list of the nodes in the graph.
+        """
+        return self.nodes.keys()
+
+    def addNode(self, name, institution, year, id, ancestors, descendents, isHead=False):
+        """
+        Add a new node to the graph if a matching node is not already
+        present.
+        """
+        record = Record(name, institution, year, id)
+        node = Node(record, ancestors, descendents)
+        self.addNodeObject(node, isHead)
+
+    def addNodeObject(self, node, isHead=False):
+        """
+        Add a new node object to the graph if a node with the same id
+        is not already present.
+        """
+        if node.id() is not None and self.hasNode(node.id()):
+            msg = "node with id %d already exists" % (node.id())
+            raise DuplicateNodeError(msg)
+        if node.id() is None:
+            # Assign a "dummy" id.
+            node.setId(self.supp_id)
+            self.supp_id -= 1
+        self.nodes[node.id()] = node
+        if self.heads is None:
+            self.heads = [node]
+        elif isHead:
+            self.heads.append(node)
+
+    def generateDotFile(self, include_ancestors, include_descendents):
+        """
+        Return a string that contains the content of the Graphviz dotfile
+        format for this graph.
+        """
+        if self.heads is None:
+            return ""
+
+        queue = []
+        for head in self.heads:
+            queue.append(head.id())
+        edges = ""
+        dotfile = ""
+        
+        dotfile += """digraph genealogy {
+    graph [charset="utf-8"];
+    node [shape=plaintext];
+    edge [style=bold];\n\n"""
+
+        while len(queue) > 0:
+            node_id = queue.pop()
+            if not self.hasNode(node_id):
+                # Skip this id if a corresponding node is not present.
+                continue
+            node = self.getNode(node_id)
+
+            if node.already_printed:
+                continue
+            else:
+                node.already_printed = True
+            
+            if include_ancestors:
+                # Add this node's advisors to queue.
+                queue += node.ancestors
+                
+            if include_descendents:
+                # Add this node's descendents to queue.
+                queue += node.descendents
+        
+            # Print this node's information.
+            nodestr = "    %d [label=\"%s\"];" % (node_id, node)
+            dotfile += nodestr
+
+            # Store the connection information for this node.
+            for advisor in node.ancestors:
+                if self.hasNode(advisor):
+                    edgestr = "\n    %d -> %d;" % (advisor, node_id)
+                    edges += edgestr
+                
+            dotfile += "\n"
+
+        # Now print the connections between the nodes.
+        dotfile += edges
+
+        dotfile += "\n}\n"
+        return dotfile
diff --git a/geneagrapher/geneagraph-cgi.py b/geneagrapher/geneagraph-cgi.py
new file mode 100644
index 0000000..482eb9b
--- /dev/null
+++ b/geneagrapher/geneagraph-cgi.py
@@ -0,0 +1,119 @@
+#!/usr/bin/python
+
+import cgi
+import random
+import os
+import time
+from grab import *
+from GGraph import *
+#import cgitb; cgitb.enable() # for debugging, comment out for production
+
+form = cgi.FieldStorage()
+name = form.getfirst("name", "")
+extra = form.getfirst("extra", "")
+nodes = form.getlist("node")
+output = form.getfirst("output", "png")
+
+# Save the input to log file.
+f = open("/var/log/geneagraph", "a")
+f.write(time.strftime('%m/%d/%Y %H:%M:%S'))
+f.write(" ")
+f.write(os.environ['REMOTE_ADDR'])
+f.write("\n")
+if name != "":
+	f.write("\tName: ")
+	f.write(name)
+	f.write("\n")
+if extra != "":
+	f.write("\tExtra: ")
+	f.write(extra)
+	f.write("\n")
+if len(nodes) > 0:
+	f.write("\t")
+	f.write(str(nodes))
+	f.write("\n")
+f.close()
+
+try:
+	if len(name) > 100:
+		raise ValueError("Name field longer than maximum allowed length (100 characters).")
+	if len(extra) > 100:
+		raise ValueError("Extra field longer than maximum allowed length (100 characters).")
+	if len(nodes) > 5:
+	#if len(nodes) > 50:
+		raise ValueError("Only five node URLs may be supplied.")
+
+# Replace special characters in name and extra with backslashed form
+	name = name.replace('\\', '\\\\')
+	name = name.replace('\"', '\\"')
+	extra = extra.replace('\\', '\\\\')
+	extra = extra.replace('"', '\\"')
+
+	record = Record(name, extra, -1, 0)
+
+	printHead = True
+	if name == "" and extra == "":
+		printHead = False
+
+	advisors = []
+	for index in range(len(nodes)):
+		if not nodes[index].isspace():
+			if nodes[index].find('id.php?id=') > -1:
+				id = nodes[index].split('id.php?id=')[1].strip()
+				if id.isdigit():
+					advisors.append(int(id))
+				else:
+					raise ValueError("Node " + str(index+1) + " was improperly formatted.")
+			else:
+				raise ValueError("Node " + str(index+1) + " was improperly formatted.")
+
+		
+	node = Node(record, advisors)
+	graph = Graph(node, printHead)
+
+	for advisor in advisors:
+		extractNodeInformation(advisor, graph)
+
+	fnum = str(int(random.random()*1000000000000000))
+	filename = '/tmp/' + fnum + '.dot'
+	graph.writeDotFile(filename)
+
+	if output == "dot":
+		print "Content-Type: text/html"
+		print
+		print "<html><body><pre>"
+		f = open(filename, "r")
+		file = f.read()
+		f.close()
+		print file
+		print "</pre></body></html>"
+	elif output == "png" or output == "ps":
+		psfilename = '/tmp/' + fnum + '.ps'
+		command = '/usr/local/bin/dot -Tps ' + filename + ' -o ' + psfilename
+		os.system(command)
+		if output == "png":
+			pngfilename = '/tmp/' + fnum + '.png'
+			command = '/usr/bin/convert -density 144 -geometry 50% ' + psfilename + ' ' + pngfilename
+			os.system(command)
+			print "Content-type: image/png"
+			print "Content-Disposition: attachment; filename=genealogy.png"
+			print
+			f = open(pngfilename, "r")
+		elif output == "ps":
+			print "Content-Type: application/postscript"
+			print
+			f = open(psfilename, "r")
+		file = f.read()
+		f.close()
+		print file
+	else: # improper output chosen
+		raise ValueError("Return type was improperly formatted. Go back and check it out.")
+
+	command = '/bin/rm /tmp/' + fnum + '.*'
+	os.system(command)
+
+except ValueError, e:
+	print "Content-type: text/html"
+	print
+	print e, "<br>Go back and check it out."
+	raise SystemExit
diff --git a/geneagrapher/geneagrapher.py b/geneagrapher/geneagrapher.py
new file mode 100644
index 0000000..ea277d5
--- /dev/null
+++ b/geneagrapher/geneagrapher.py
@@ -0,0 +1,149 @@
+from optparse import OptionParser
+import GGraph
+import grab
+
+class Geneagrapher:
+	"""
+	A class for building Graphviz "dot" files for math genealogies
+	extracted from the Mathematics Genealogy Project website.
+	"""
+	def __init__(self):
+		self.graph = GGraph.Graph()
+		self.leaf_ids = []
+		self.get_ancestors = False
+		self.get_descendents = False
+		self.verbose = False
+		self.supp_node_filename = None
+		self.write_filename = None
+
+	def parseInput(self):
+		"""
+		Parse command-line information.
+		"""
+		self.parser = OptionParser()
+
+		self.parser.set_usage("%prog [options] ID ...")
+		self.parser.set_description('Create a Graphviz "dot" file for a mathematics genealogy, where ID is a record identifier from the Mathematics Genealogy Project. Multiple IDs may be passed.')
+
+		self.parser.add_option("-f", "--file", dest="filename",
+				       help="write report to FILE [default: stdout]", metavar="FILE", default=None)
+		self.parser.add_option("-a", "--with-ancestors", action="store_true", dest="get_ancestors",
+				       default=False, help="do not get ancestors of any input IDs")
+		self.parser.add_option("-d", "--with-descendents", action="store_true", dest="get_descendents",
+				       default=False, help="do not get ancestors of any input IDs")
+		self.parser.add_option("--verbose", "-v", action="store_true", dest="verbose", default=False,
+				       help="print information showing progress")
+		self.parser.add_option("--version", "-V", action="store_true", dest="print_version", default=False,
+				       help="print geneagrapher version and exit")
+		self.parser.add_option("-n", "--attach-node-file", dest="supp_node_filename", metavar="FILE",
+				       help="attach supplementary nodes returned by function 'define_supp_nodes()' in FILE to the graph", default=None)
+
+		(options, args) = self.parser.parse_args()
+		
+		if options.print_version:
+			print "Geneagrapher Version 0.2"
+			self.parser.exit()
+		
+		if len(args) == 0:
+			raise SyntaxError("%s: error: no record IDs passed" % (self.parser.get_prog_name()))
+
+		self.get_ancestors = options.get_ancestors
+		self.get_descendents = options.get_descendents
+		self.verbose = options.verbose
+		self.supp_node_filename = options.supp_node_filename
+		self.write_filename = options.filename
+		for arg in args:
+			self.leaf_ids.append(int(arg))
+		
+	def buildGraph(self):
+		"""
+		Populate the graph member by grabbing the mathematician
+		pages and extracting relevant data.
+		"""
+		leaf_grab_queue = list(self.leaf_ids)
+		ancestor_grab_queue = []
+		descendent_grab_queue = []
+
+		# Grab "supplementary" nodes from file.
+		if self.supp_node_filename is not None:
+			supp_node_modname = self.supp_node_filename.split('.')[0]
+			importstr = "import %s" % (supp_node_modname)
+			exec(importstr)
+			if "define_supp_nodes" not in dir(eval(supp_node_modname)):
+				errstr = "'%s' module has no function 'define_supp_nodes'" % (supp_node_modname)
+				raise AttributeError(errstr)
+			supp_nodes = eval(supp_node_modname).define_supp_nodes()
+			for node in supp_nodes:
+				self.graph.addNodeObject(node, True)
+		
+		# Grab "leaf" nodes.
+		while len(leaf_grab_queue) != 0:
+			id = leaf_grab_queue.pop()
+			if not self.graph.hasNode(id):
+				# Then this information has not yet been grabbed.
+				grabber = grab.Grabber(id)
+				if self.verbose:
+					print "Grabbing record #%d" % (id)
+				try:
+					[name, institution, year, advisors, descendents] = grabber.extractNodeInformation()
+				except ValueError:
+					# The given id does not exist in the Math Genealogy Project's database.
+					raise
+				self.graph.addNode(name, institution, year, id, advisors, descendents, True)
+				if self.get_ancestors:
+					ancestor_grab_queue += advisors
+				if self.get_descendents:
+					descendent_grab_queue += descendents
+
+		# Grab ancestors of leaf nodes.
+		if self.get_ancestors:
+			while len(ancestor_grab_queue) != 0:
+				id = ancestor_grab_queue.pop()
+				if not self.graph.hasNode(id):
+					# Then this information has not yet been grabbed.
+					grabber = grab.Grabber(id)
+					if self.verbose:
+						print "Grabbing record #%d" % (id)
+					try:
+						[name, institution, year, advisors, descendents] = grabber.extractNodeInformation()
+					except ValueError:
+						# The given id does not exist in the Math Genealogy Project's database.
+						raise
+					self.graph.addNode(name, institution, year, id, advisors, descendents)
+					ancestor_grab_queue += advisors
+						
+		# Grab descendents of leaf nodes.
+		if self.get_descendents:
+			while len(descendent_grab_queue) != 0:
+				id = descendent_grab_queue.pop()
+				if not self.graph.hasNode(id):
+					# Then this information has not yet been grabbed.
+					grabber = grab.Grabber(id)
+					if self.verbose:
+						print "Grabbing record #%d" % (id)
+					try:
+						[name, institution, year, advisors, descendents] = grabber.extractNodeInformation()
+					except ValueError:
+						# The given id does not exist in the Math Genealogy Project's database.
+						raise
+					self.graph.addNode(name, institution, year, id, advisors, descendents)
+					descendent_grab_queue += descendents
+					
+	def generateDotFile(self):
+		dotfile = self.graph.generateDotFile(self.get_ancestors, self.get_descendents)
+		if self.write_filename is not None:
+			outfile = open(self.write_filename, "w")
+			outfile.write(dotfile)
+			outfile.close()
+		else:
+			print dotfile
+		
+if __name__ == "__main__":
+	geneagrapher = Geneagrapher()
+	try:
+		geneagrapher.parseInput()
+	except SyntaxError, e:
+		print geneagrapher.parser.get_usage()
+		print e
+	geneagrapher.buildGraph()
+	geneagrapher.generateDotFile()
diff --git a/geneagrapher/grab.py b/geneagrapher/grab.py
new file mode 100644
index 0000000..7825fbc
--- /dev/null
+++ b/geneagrapher/grab.py
@@ -0,0 +1,79 @@
+import urllib
+import re
+from htmlentitydefs import name2codepoint
+
+class Grabber:
+    """
+    Class for grabbing and parsing mathematician information from
+    Math Genealogy Database.
+    """
+    def __init__(self, id):
+        self.id = id
+        self.pagestr = None
+        self.name = None
+        self.institution = None
+        self.year = None
+        self.advisors = []
+        self.descendents = []
+
+    def unescape(self, s):
+        return re.sub('&(%s);' % '|'.join(name2codepoint),\
+                      lambda m: unichr(name2codepoint[m.group(1)]), s)
+
+    def getPage(self):
+        """
+        Grab the page for self.id from the Math Genealogy Database.
+        """
+        if self.pagestr is None:
+            url = 'http://genealogy.math.ndsu.nodak.edu/id.php?id=' + str(self.id)
+            page = urllib.urlopen(url)
+            self.pagestr = page.read()
+            self.pagestr = self.pagestr.decode('utf-8')
+            
+    def extractNodeInformation(self):
+        """
+        For the mathematician in this object, extract the list of
+        advisor ids, the mathematician name, the mathematician
+        institution, and the year of the mathematician's degree.
+        """
+        if self.pagestr is None:
+            self.getPage()
+            
+        self.advisors = []
+        self.descendents = []
+
+        # Split the page string at newline characters.
+        psarray = self.pagestr.split('\n')
+        
+        if psarray[0].find("An error occurred in the forwarding block") > -1:
+            # Then a bad URL (e.g., a bad record id) was given. Throw an exception.
+            msg = "Invalid page address for id %d" % (self.id)
+            raise ValueError(msg)
+
+        lines = iter(psarray)
+        for line in lines:
+            if line.find('h2 style=') > -1:
+                line = lines.next()
+                self.name = self.unescape(line.split('</h2>')[0].strip())
+
+            if '#006633; margin-left: 0.5em">' in line:
+                inst_year = line.split('#006633; margin-left: 0.5em">')[1].split("</span>")[:2]
+                self.institution = self.unescape(inst_year[0].strip())
+                if self.institution == u"":
+                    self.institution = None
+                if inst_year[1].split(',')[0].strip().isdigit():
+                    self.year = int(inst_year[1].split(',')[0].strip())
+
+            if 'Advisor' in line:
+                if 'a href=\"id.php?id=' in line:
+                    # Extract link to advisor page.
+                    advisor_id = int(line.split('a href=\"id.php?id=')[1].split('\">')[0])
+                    self.advisors.append(advisor_id)
+
+            if '<tr ' in line:
+                descendent_id = int(line.split('a href=\"id.php?id=')[1].split('\">')[0])
+                self.descendents.append(descendent_id)
+                
+            if 'According to our current on-line database' in line:
+                break
+        return [self.name, self.institution, self.year, self.advisors, self.descendents]
diff --git a/geneagrapher/tests.py b/geneagrapher/tests.py
new file mode 100644
index 0000000..a5ac852
--- /dev/null
+++ b/geneagrapher/tests.py
@@ -0,0 +1,454 @@
+import sys
+import unittest
+import GGraph
+import grab
+import geneagrapher
+
+# Unit tests for GGraph.
+class TestRecordMethods(unittest.TestCase):
+    """
+    Unit tests for the GGraph.Record class.
+    """
+    def test001_init(self):
+        # Test the constructor.
+        record = GGraph.Record("Carl Friedrich Gauss", "Universitaet Helmstedt", 1799, 18231)
+        self.assertEqual(record.name, "Carl Friedrich Gauss")
+        self.assertEqual(record.institution, "Universitaet Helmstedt")
+        self.assertEqual(record.year, 1799)
+        self.assertEqual(record.id, 18231)
+        
+    def test002_init_bad_name(self):
+        # Test constructor with bad 'name' parameter.
+        self.assertRaises(TypeError, GGraph.Record, 1, "Universitaet Helmstedt", 1799, 18231)
+        
+    def test003_init_bad_institution(self):
+        # Test constructor with bad 'institution' parameter.
+        self.assertRaises(TypeError, GGraph.Record, "Carl Friedrich Gauss", 1, 1799, 18231)
+        
+    def test004_init_bad_year(self):
+        # Test constructor with bad 'year' parameter.
+        self.assertRaises(TypeError, GGraph.Record, "Carl Friedrich Gauss",
+                          "Universitaet Helmstedt", "1799", 18231)
+        
+    def test005_init_bad_id(self):
+        # Test constructor with bad 'id' parameter.
+        self.assertRaises(TypeError, GGraph.Record, "Carl Friedrich Gauss",
+                          "Universitaet Helmstedt", 1799, "18231")
+        
+    def test006_cmp_equal(self):
+        # Verify two 'equal' records are compared correctly.
+        record1 = GGraph.Record("Carl Friedrich Gauss", "Universitaet Helmstedt", 1799, 18231)
+        record2 = GGraph.Record("Carl Friedrich Gauss", "Universitaet Helmstedt", 1799, 18231)
+        self.assert_(record1 == record2)
+        
+    def test007_cmp_unequal(self):
+        # Verify two 'unequal' records are compared correctly.
+        record1 = GGraph.Record("Carl Friedrich Gauss", "Universitaet Helmstedt", 1799, 18231)
+        record2 = GGraph.Record("Leonhard Euler", "Universitaet Basel", 1726, 38586)
+        self.assert_(record1 < record2)
+
+    def test008_hasInstitution_yes(self):
+        # Verify hasInstitution() method returns True when the conditions are right.
+        record = GGraph.Record("Carl Friedrich Gauss", "Universitaet Helmstedt", 1799, 18231)
+        self.assert_(record.hasInstitution())
+
+    def test009_hasInstitution_no(self):
+        # Verify hasInstitution() method returns False when the conditions are right.
+        record = GGraph.Record("Carl Friedrich Gauss", None, 1799, 18231)
+        self.assert_(not record.hasInstitution())
+
+    def test010_hasYear_yes(self):
+        # Verify hasYear() method returns True when the conditions are right.
+        record = GGraph.Record("Carl Friedrich Gauss", "Universitaet Helmstedt", 1799, 18231)
+        self.assert_(record.hasYear())
+
+    def test011_hasYear_no(self):
+        # Verify hasYear() method returns False when the conditions are right.
+        record = GGraph.Record("Carl Friedrich Gauss", "Universitaet Helmstedt", None, 18231)
+        self.assert_(not record.hasYear())
+
+class TestNodeMethods(unittest.TestCase):
+    """
+    Unit tests for the GGraph.Node class.
+    """
+    def setUp(self):
+        self.record = GGraph.Record("Carl Friedrich Gauss", "Universitaet Helmstedt", 1799, 18231)
+    
+    def test001_init(self):
+        # Test the constructor.
+        node = GGraph.Node(self.record, [], [])
+        self.assertEquals(node.record, self.record)
+        self.assertEquals(node.ancestors, [])
+        self.assertEquals(node.descendents, [])
+        
+    def test002_init_bad_record(self):
+        # Test the constructor for a case where the record passed is not a Record
+        # object.
+        self.assertRaises(TypeError, GGraph.Node, 1, [], [])
+        
+    def test003_init_bad_ancestor_list(self):
+        # Test the constructor for a case where the ancestor list is not a list.
+        self.assertRaises(TypeError, GGraph.Node, self.record, 1, [])
+
+    def test003_2_init_bad_descendent_list(self):
+        # Test the constructor for a case where the descendent list is not a list.
+        self.assertRaises(TypeError, GGraph.Node, self.record, [], 1)
+        
+    def test004_str_full(self):
+        # Test __str__() method for Node with complete record.
+        node = GGraph.Node(self.record, [], [])
+        nodestr = node.__str__()
+        nodestrexpt = "Carl Friedrich Gauss \\nUniversitaet Helmstedt (1799)"
+        self.assertEquals(nodestr, nodestrexpt)
+
+    def test005_str_no_year(self):
+        # Test __str__() method for Node containing record without year.
+        record = GGraph.Record("Carl Friedrich Gauss", "Universitaet Helmstedt", None, 18231)
+        node = GGraph.Node(record, [], [])
+        nodestr = node.__str__()
+        nodestrexpt = "Carl Friedrich Gauss \\nUniversitaet Helmstedt"
+        self.assertEquals(nodestr, nodestrexpt)
+
+    def test006_str_no_inst(self):
+        # Test __str__() method for Node containing record without institution.
+        record = GGraph.Record("Carl Friedrich Gauss", None, 1799, 18231)
+        node = GGraph.Node(record, [], [])
+        nodestr = node.__str__()
+        nodestrexpt = "Carl Friedrich Gauss \\n(1799)"
+        self.assertEquals(nodestr, nodestrexpt)
+
+    def test007_str_no_inst_no_id(self):
+        # Test __str__() method for Node containing record without institution
+        # or year.
+        record = GGraph.Record("Carl Friedrich Gauss", None, None, 18231)
+        node = GGraph.Node(record, [], [])
+        nodestr = node.__str__()
+        nodestrexpt = "Carl Friedrich Gauss"
+        self.assertEquals(nodestr, nodestrexpt)
+
+    def test008_cmp_equal(self):
+        # Test comparison method for Nodes with identical records.
+        record2 = GGraph.Record("Carl Friedrich Gauss", "Universitaet Helmstedt", 1799, 18231)
+        node1 = GGraph.Node(self.record, [], [])
+        node2 = GGraph.Node(record2, [], [])
+        self.assert_(node1 == node2)
+
+    def test009_cmp_unequal(self):
+        # Test comparison method for Nodes with different records.
+        record2 = GGraph.Record("Leonhard Euler", "Universitaet Basel", 1726, 38586)
+        node1 = GGraph.Node(self.record, [], [])
+        node2 = GGraph.Node(record2, [], [])
+        self.assert_(node1 < node2)
+
+    def test010_add_ancestor(self):
+        # Test the addAncestor() method.
+        node = GGraph.Node(self.record, [], [])
+        node.addAncestor(5)
+        self.assertEquals(node.ancestors, [5])
+
+    def test011_add_ancestor_bad_type(self):
+        # Test the addAncestor() method for a case where the parameter type is incorrect.
+        node = GGraph.Node(self.record, [], [])
+        self.assertRaises(TypeError, node.addAncestor, '5')
+        
+    def test012_get_id(self):
+        node = GGraph.Node(self.record, [], [])
+        self.assertEquals(node.id(), 18231)
+
+    def test013_set_id(self):
+        # Test the setId() method.
+        node = GGraph.Node(self.record, [], [])
+        self.assertEquals(node.id(), 18231)
+        node.setId(15)
+        self.assertEquals(node.id(), 15)
+        
+
+class TestGraphMethods(unittest.TestCase):
+    """
+    Unit tests for the GGraph.Graph class.
+    """
+    def setUp(self):
+        self.record1 = GGraph.Record("Carl Friedrich Gauss", "Universitaet Helmstedt", 1799, 18231)
+        self.node1 = GGraph.Node(self.record1, [], [])
+        self.graph1 = GGraph.Graph([self.node1])
+    
+    def test001_init_empty(self):
+        # Test the constructor.
+        graph = GGraph.Graph()
+        self.assertEquals(graph.heads, None)
+        
+    def test002_init(self):
+        # Test the constructor.
+        self.assert_(self.graph1.heads == [self.node1])
+        self.assertEquals(self.graph1.nodes.keys(), [18231])
+        self.assertEquals(self.graph1.nodes[18231], self.node1)
+        
+    def test003_init_bad_heads(self):
+        # Test the constructor when passed a bad type for the heads parameter.
+        self.assertRaises(TypeError, GGraph.Graph, 3)
+        
+    def test004_has_node_true(self):
+        # Test the hasNode() method for a True case.
+        self.assertEquals(self.graph1.hasNode(18231), True)
+        
+    def test005_has_node_false(self):
+        # Test the hasNode() method for a False case.
+        self.assertEquals(self.graph1.hasNode(1), False)
+        
+    def test006_get_node(self):
+        # Test the getNode() method.
+        node = self.graph1.getNode(18231)
+        self.assert_(node == self.node1)
+        
+    def test007_get_node_not_found(self):
+        # Test the getNode() method for a case where the node does not exist.
+        node = self.graph1.getNode(1)
+        self.assertEquals(node, None)
+        
+    def test008_get_node_list(self):
+        # Test the getNodeList() method.
+        self.assertEquals(self.graph1.getNodeList(), [18231])
+        
+    def test008_get_node_list_empty(self):
+        # Test the getNodeList() method for an empty graph.
+        graph = GGraph.Graph()
+        self.assertEquals(graph.getNodeList(), [])
+        
+    def test009_add_node(self):
+        # Test the addNode() method.
+        self.graph1.addNode("Leonhard Euler", "Universitaet Basel", 1726, 38586, [], [])
+        self.assertEquals([38586, 18231], self.graph1.getNodeList())
+        self.assertEquals(self.graph1.heads, [self.node1])
+
+    def test010_add_second_node_head(self):
+        # Test the addNode() method when adding a second node and
+        # marking it as a head node.
+        self.graph1.addNode("Leonhard Euler", "Universitaet Basel", 1726, 38586, [], [], True)
+        self.assertEquals([38586, 18231], self.graph1.getNodeList())
+        self.assertEquals(self.graph1.heads, [self.node1, self.graph1.getNode(38586)])
+
+    def test011_add_node_head(self):
+        # Test the addNode() method when no heads exist.
+        graph = GGraph.Graph()
+        self.assertEquals(graph.heads, None)
+        graph.addNode("Leonhard Euler", "Universitaet Basel", 1726, 38586, [], [])
+        self.assertEquals(graph.heads, [graph.getNode(38586)])
+
+    def test012_add_node_already_present(self):
+        self.graph1.addNode("Leonhard Euler", "Universitaet Basel", 1726, 38586, [], [])
+        self.assertEquals([38586, 18231], self.graph1.getNodeList())
+        self.assertRaises(GGraph.DuplicateNodeError, self.graph1.addNode, "Leonhard Euler", "Universitaet Basel", 1726, 38586, [], [])
+
+    def test013_add_node_object(self):
+        # Test the addNodeObject() method.
+        record = GGraph.Record("Leonhard Euler", "Universitaet Basel", 1726, 38586)
+        node = GGraph.Node(record, [], [])
+        self.graph1.addNodeObject(node)
+        self.assertEquals([38586, 18231], self.graph1.getNodeList())
+        self.assertEquals(self.graph1.heads, [self.node1])
+
+    def test014_generate_dot_file(self):
+        # Test the generateDotFile() method.
+        dotfileexpt = """digraph genealogy {
+    graph [charset="utf-8"];
+    node [shape=plaintext];
+    edge [style=bold];
+
+    18231 [label="Carl Friedrich Gauss \\nUniversitaet Helmstedt (1799)"];
+
+}
+"""    
+        dotfile = self.graph1.generateDotFile(True, False)
+        self.assertEquals(dotfile, dotfileexpt)
+        
+    def test015_generate_dot_file(self):
+        # Test the generateDotFile() method.
+        graph = GGraph.Graph()
+        graph.addNode("Carl Friedrich Gauss", "Universitaet Helmstedt", 1799, 18231, [18230], [])
+        graph.addNode("Johann Friedrich Pfaff", "Georg-August-Universitaet Goettingen", 1786, 18230, [66476], [])
+        graph.addNode("Abraham Gotthelf Kaestner", "Universitaet Leipzig", 1739, 66476, [57670], [])
+        graph.addNode("Christian August Hausen", "Martin-Luther-Universitaet Halle-Wittenberg", 1713, 57670, [72669], [])
+        graph.addNode("Johann Christoph Wichmannshausen", "Universitaet Leipzig", 1685, 72669, [21235], [])
+        graph.addNode("Otto Mencke", "Universitaet Leipzig", 1665, 21235, [], [])
+        
+        dotfileexpt = """digraph genealogy {
+    graph [charset="utf-8"];
+    node [shape=plaintext];
+    edge [style=bold];
+
+    18231 [label="Carl Friedrich Gauss \\nUniversitaet Helmstedt (1799)"];
+    18230 [label="Johann Friedrich Pfaff \\nGeorg-August-Universitaet Goettingen (1786)"];
+    66476 [label="Abraham Gotthelf Kaestner \\nUniversitaet Leipzig (1739)"];
+    57670 [label="Christian August Hausen \\nMartin-Luther-Universitaet Halle-Wittenberg (1713)"];
+    72669 [label="Johann Christoph Wichmannshausen \\nUniversitaet Leipzig (1685)"];
+    21235 [label="Otto Mencke \\nUniversitaet Leipzig (1665)"];
+
+    18230 -> 18231;
+    66476 -> 18230;
+    57670 -> 66476;
+    72669 -> 57670;
+    21235 -> 72669;
+}
+"""
+        dotfile = graph.generateDotFile(True, False)
+        self.assertEquals(dotfile, dotfileexpt)
+
+class TestGrabberMethods(unittest.TestCase):
+    """
+    Unit tests for the grab.Grabber class.
+    """
+    def setUp(self):
+        self.grabber = grab.Grabber(18231)
+        
+    def test001_init(self):
+        # Test constructor.
+        self.assertEquals(self.grabber.id, 18231)
+        self.assertEquals(self.grabber.pagestr, None)
+        self.assertEquals(self.grabber.name, None)
+        self.assertEquals(self.grabber.institution, None)
+        self.assertEquals(self.grabber.year, None)
+        self.assertEquals(self.grabber.advisors, [])
+        self.assertEquals(self.grabber.descendents, [])
+
+    def test002_get_page(self):
+        # Test getPage() method.
+        self.grabber.getPage()
+        self.assert_(self.grabber.pagestr is not None)
+        self.assert_(u"<title>The Mathematics Genealogy Project - Carl Gau\xdf</title>" in self.grabber.pagestr)
+        # Get page again and test for adverse affects.
+        self.grabber.getPage()
+        self.assert_(u"<title>The Mathematics Genealogy Project - Carl Gau\xdf</title>" in self.grabber.pagestr)
+
+    def test003_extract_info_bad(self):
+        # Verify exception thrown for bad id.
+        grabber = grab.Grabber(999999999)
+        self.assertRaises(ValueError, grabber.extractNodeInformation)
+        
+    def test004_extract_info_all_fields(self):
+        # Test the extractNodeInformation() method for a record containing all fields.
+        [name, institution, year, advisors, descendents] = self.grabber.extractNodeInformation()
+        self.assertEquals(name, self.grabber.name)
+        self.assertEquals(institution, self.grabber.institution)
+        self.assertEquals(year, self.grabber.year)
+        self.assertEquals(advisors, self.grabber.advisors)
+        self.assertEquals(name, u"Carl Friedrich Gau\xdf")
+        self.assertEquals(institution, u"Universit\xe4t Helmstedt")
+        self.assertEquals(year, 1799)
+        self.assertEquals(advisors, [18230])
+        self.assertEquals(descendents, [18603, 18233, 62547, 29642, 55175, 29458, 19953, 18232])
+        
+        # Verify calling extractNodeInformation() twice does not have side effect.
+        [name, institution, year, advisors, descendents] = self.grabber.extractNodeInformation()
+        self.assertEquals(name, u"Carl Friedrich Gau\xdf")
+        self.assertEquals(institution, u"Universit\xe4t Helmstedt")
+        self.assertEquals(year, 1799)
+        self.assertEquals(advisors, [18230])
+        self.assertEquals(descendents, [18603, 18233, 62547, 29642, 55175, 29458, 19953, 18232])
+        
+    def test005_extract_info_no_advisor(self):
+        # Test the extractNodeInformation() method for a record with no advisor.
+        grabber = grab.Grabber(21235)
+        [name, institution, year, advisors, descendents] = grabber.extractNodeInformation()
+        self.assertEquals(name, u"Otto  Mencke")
+        self.assertEquals(institution, u"Universit\xe4t Leipzig")
+        self.assertEquals(year, 1665)
+        self.assertEquals(advisors, [])
+        self.assertEquals(descendents, [77909, 72669])
+        
+    def test006_extract_info_no_year(self):
+        # Test the extractNodeInformation() method for a record with no year.
+        # This example also has no descendents.
+        grabber = grab.Grabber(53658)
+        [name, institution, year, advisors, descendents] = grabber.extractNodeInformation()
+        self.assertEquals(name, u"S.  Cingolani")
+        self.assertEquals(institution, u"Universit\xe0 di Pisa")
+        self.assertEquals(year, None)
+        self.assertEquals(advisors, [51261])
+        self.assertEquals(descendents, [])
+        
+    def test007_extract_info_no_inst(self):
+        # Test the extractNodeInformation() method for a record with no institution.
+        # This test is also missing additional information already tested.
+        grabber = grab.Grabber(52965)
+        [name, institution, year, advisors, descendents] = grabber.extractNodeInformation()
+        self.assertEquals(name, u"Walter  Mayer")
+        self.assertEquals(institution, None)
+        self.assertEquals(year, None)
+        self.assertEquals(advisors, [])
+        self.assertEquals(descendents, [52996])
+
+    # Tests for special (from my point of view) characters:
+    def test008_slash_l(self):
+        # Test the extractNodeInformation() method for a record
+        # containing a slash l character. Example:
+        # http://www.genealogy.math.ndsu.nodak.edu/id.php?id=7383.
+        grabber = grab.Grabber(7383)
+        [name, institution, year, advisors, descendents] = grabber.extractNodeInformation()
+        self.assertEquals(name, u"W\u0142adys\u0142aw Hugo Dyonizy Steinhaus")
+        self.assertEquals(institution, u"Georg-August-Universit\xe4t G\xf6ttingen")
+        self.assertEquals(year, 1911)
+        self.assertEquals(advisors, [7298])
+        self.assertEquals(descendents, [12681, 28292, 10275, 79297, 36991, 17851, 51907, 15165, 89841, 84016])
+
+class TestGeneagrapherMethods(unittest.TestCase):
+    """
+    Unit tests for the geneagrapher.Geneagrapher class.
+    """
+    def setUp(self):
+        self.ggrapher = geneagrapher.Geneagrapher()
+        
+    def test001_init(self):
+        # Test constructor.
+        self.assertEquals(isinstance(self.ggrapher.graph, GGraph.Graph), True)
+        self.assertEquals(self.ggrapher.leaf_ids, [])
+        self.assertEquals(self.ggrapher.get_ancestors, False)
+        self.assertEquals(self.ggrapher.get_descendents, False)
+        self.assertEquals(self.ggrapher.verbose, False)
+        self.assertEquals(self.ggrapher.supp_node_filename, None)
+        self.assertEquals(self.ggrapher.write_filename, None)
+        
+    def test002_parse_empty(self):
+        # Test parseInput() with no arguments.
+        sys.argv = ['geneagrapher']
+        self.assertRaises(SyntaxError, self.ggrapher.parseInput)
+        
+    def test003_parse_default(self):
+        # Test parseInput() with no options.
+        sys.argv = ['geneagrapher', '3']
+        self.ggrapher.get_ancestors = False
+        self.ggrapher.get_descendents = True
+        self.ggrapher.write_filename = "filler"
+        self.ggrapher.parseInput()
+        self.assertEquals(self.ggrapher.get_ancestors, False)
+        self.assertEquals(self.ggrapher.get_descendents, False)
+        self.assertEquals(self.ggrapher.verbose, False)
+        self.assertEquals(self.ggrapher.supp_node_filename, None)
+        self.assertEquals(self.ggrapher.write_filename, None)
+        self.assertEquals(self.ggrapher.leaf_ids, [3])
+
+    def test004_parse_options(self):
+        # Test parseInput() with options.
+        sys.argv = ['geneagrapher', '--with-ancestors', '--with-descendents', '--file=filler', '--verbose', '-n', 'suppfiller', '3', '43']
+        self.ggrapher.parseInput()
+        self.assertEquals(self.ggrapher.get_ancestors, True)
+        self.assertEquals(self.ggrapher.get_descendents, True)
+        self.assertEquals(self.ggrapher.verbose, True)
+        self.assertEquals(self.ggrapher.supp_node_filename, "suppfiller")
+        self.assertEquals(self.ggrapher.write_filename, "filler")
+        self.assertEquals(self.ggrapher.leaf_ids, [3, 43])
+
+    def test005_bad_supp_node_file(self):
+        # Test buildGraph() method when given bad supplementary node
+        # file.
+        sys.argv = ['geneagrapher', '--attach-node-file=tests.py', '3', '43']
+        self.ggrapher.parseInput()
+        self.assertRaises(AttributeError, self.ggrapher.buildGraph)
+
+if __name__ == '__main__':
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(TestRecordMethods))
+    suite.addTest(unittest.makeSuite(TestNodeMethods))
+    suite.addTest(unittest.makeSuite(TestGraphMethods))
+    #suite.addTest(unittest.makeSuite(TestGrabberMethods))
+    suite.addTest(unittest.makeSuite(TestGeneagrapherMethods))
+    unittest.TextTestRunner(verbosity=1).run(suite)

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-science/packages/geneagrapher.git



More information about the debian-science-commits mailing list