248 lines
7.5 KiB
Python
248 lines
7.5 KiB
Python
# ranks.py
|
|
"""
|
|
Branches and ranks objects.
|
|
|
|
Objects for representing the ranks of a hierarchy, with the
|
|
possibility of branching at nodes with redefined ranks via
|
|
the 'project_schema' directives in project YAML files.
|
|
"""
|
|
|
|
import numbers
|
|
|
|
class Branch(object):
|
|
"""
|
|
Branch represents a ranking system in the tree of schemas.
|
|
|
|
It takes the name of the project_unit where the ranking system
|
|
is overridden, and controls how all ranks defined within it
|
|
are interpreted.
|
|
"""
|
|
def __init__(self, parent, code, start, ranks):
|
|
self.parent = parent
|
|
self.code = str(code)
|
|
self.start = int(start)
|
|
|
|
self._ranks = RankList(self,[])
|
|
if parent:
|
|
for rank in parent.ranks:
|
|
if int(rank) < start:
|
|
self._ranks.append(rank)
|
|
|
|
for num, name in enumerate(ranks):
|
|
rank = Rank(self, num + start, name)
|
|
self._ranks.append(rank)
|
|
|
|
def __repr__(self):
|
|
ranklist = ', '.join([str(r) for r in self.ranks[1:]])
|
|
if self.code:
|
|
code = self.code
|
|
else:
|
|
code = 'trunk'
|
|
return "<branch '%s': %s>" % (code, ranklist)
|
|
|
|
def __contains__(self, other):
|
|
if isinstance(other, Rank) and other in self._ranks:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def rank(self, n):
|
|
"""
|
|
Coerce int or string to rank, if it matches a rank in this Branch.
|
|
"""
|
|
if isinstance(n, int) and 0 < n < len(self._ranks):
|
|
return self._ranks[n]
|
|
elif isinstance(n, str):
|
|
for rank in self._ranks:
|
|
if str(rank) == n:
|
|
return rank
|
|
else:
|
|
raise TypeError
|
|
|
|
@property
|
|
def ranks(self):
|
|
# Read-only property.
|
|
return self._ranks
|
|
|
|
|
|
|
|
class Rank(object):
|
|
"""
|
|
Ranks are named numbers indicating the position in a hierarchy.
|
|
|
|
They can be incremented and decremented. The value 0 represents the top
|
|
rank, with increasing numbers indicating finer grades in taxonomic rank.
|
|
|
|
They can have integers added to or subtracted from them, meaning to go
|
|
down or up in rank. They cannot be added to each other, though. Note that
|
|
higher and lower rank in the real world since of more or less scope is
|
|
inverted as a number -- the lower rank has a higher number.
|
|
|
|
There are upper and lower bounds to rank, defined by the schema 'Branch'.
|
|
|
|
Coercing a rank to an integer (int()) returns the numeric rank.
|
|
|
|
Coercing a rank to a string (str()) returns the rank name.
|
|
|
|
The representation includes the branch name and rank name.
|
|
|
|
Ranks can be subtracted, returning an integer representing the difference
|
|
in rank.
|
|
"""
|
|
def __init__(self, branch, num, name):
|
|
self.num = num
|
|
self.name = name
|
|
self.branch = branch
|
|
|
|
def __index__(self):
|
|
return self.num
|
|
|
|
def __int__(self):
|
|
return self.num
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
def __repr__(self):
|
|
return '<%s:%d-%s>' % (self.branch.code, self.num, self.name)
|
|
|
|
def __hash__(self):
|
|
return hash((self.branch, self.num, self.name))
|
|
|
|
def __eq__(self, other):
|
|
if isinstance(other, Rank):
|
|
if hash(self) == hash(other):
|
|
return True
|
|
else:
|
|
return False
|
|
elif isinstance(other, str):
|
|
if other == self.name:
|
|
return True
|
|
else:
|
|
return False
|
|
elif isinstance(other, int):
|
|
if other == self.num:
|
|
return True
|
|
else:
|
|
return False
|
|
else:
|
|
return False
|
|
|
|
def __gt__(self, other):
|
|
if isinstance(other, Rank):
|
|
if self.num > other.num:
|
|
return True
|
|
else:
|
|
return False
|
|
elif isinstance(other, int):
|
|
if self.num > other:
|
|
return True
|
|
else:
|
|
return False
|
|
else:
|
|
raise TypeError("Rank can't compare to %s" % type(other))
|
|
|
|
def __lt__(self, other):
|
|
if isinstance(other, Rank):
|
|
if self.num < other.num:
|
|
return True
|
|
else:
|
|
return False
|
|
elif isinstance(other, int):
|
|
if self.num < other:
|
|
return True
|
|
else:
|
|
return False
|
|
else:
|
|
raise TypeError("Rank can't compare to %s" % type(other))
|
|
|
|
def __ge__(self, other):
|
|
return (self > other or self == other)
|
|
|
|
def __le__(self, other):
|
|
return (self < other or self == other)
|
|
|
|
|
|
def __add__(self, other):
|
|
if isinstance(other, int):
|
|
if (self.num + other) < len(self.branch.ranks):
|
|
return self.branch.ranks[self.num+other]
|
|
elif (self.num + other) < 1:
|
|
return trunk
|
|
else:
|
|
return None
|
|
else:
|
|
raise TypeError("Changes in rank must be integers.")
|
|
|
|
def __radd__(self, other):
|
|
return self.__add__(other)
|
|
|
|
def __sub__(self, other):
|
|
if isinstance(other, Rank):
|
|
return (self.num - other.num)
|
|
elif isinstance(other, int):
|
|
if 0 < (self.num - other) < len(self.branch.ranks):
|
|
return self.branch.ranks[self.num-other]
|
|
elif (self.num - other) < 1:
|
|
return trunk
|
|
elif (self.num - other) > len(self.branch.ranks):
|
|
return None
|
|
else:
|
|
raise TypeError("Rank subtraction not defined for %s" % type(other))
|
|
|
|
def __rsub__(self, other):
|
|
if isinstance(other, Rank):
|
|
return (other.num - self.num)
|
|
else:
|
|
raise TypeError("Rank subtraction not defined for %s" % type(other))
|
|
|
|
|
|
class RankList(list):
|
|
"""
|
|
Convenience wrapper for a list of ranks, with simplified look-up.
|
|
|
|
This allows indexes and slices on the ranks to use Rank objects and/or
|
|
string names in addition to integers for accessing the elements of the
|
|
list.
|
|
|
|
The RankList also has to know what branch it is indexing, so it includes
|
|
a 'branch' attribute.
|
|
"""
|
|
def __init__(self, branch, ranks):
|
|
self.branch = branch
|
|
for rank in ranks:
|
|
self.append(rank)
|
|
|
|
def __getitem__(self, rank):
|
|
if isinstance(rank, Rank):
|
|
i = int(rank)
|
|
elif isinstance(rank, str):
|
|
i = [r.name for r in self].index(rank)
|
|
elif isinstance(rank, int):
|
|
i = rank
|
|
elif isinstance(rank, slice):
|
|
if rank.start is None:
|
|
j = 0
|
|
else:
|
|
j = self.__getitem__(rank.start)
|
|
|
|
if rank.stop is None:
|
|
k = len(self)
|
|
else:
|
|
k = self.__getitem__(rank.stop)
|
|
|
|
s = []
|
|
for i in range(j,k):
|
|
s.append(super().__getitem__(i))
|
|
|
|
return s
|
|
else:
|
|
raise IndexError("Type %s not a valid index for RankList" % type(rank))
|
|
return super().__getitem__(i)
|
|
|
|
|
|
# Define the trunk branch object
|
|
# This schema will make sense for any unaffiliated Blender document,
|
|
# even if it hasn't been saved as a file yet:
|
|
trunk = Branch(None, '', 0, ('', 'file', 'scene'))
|