25
24
_SIGNATURE = "Bazaar Graph Index 1\n"
28
_whitespace_re = re.compile('[\t\n\x0b\x0c\r\x00 ]')
29
_newline_null_re = re.compile('[\n\0]')
32
27
class GraphIndexBuilder(object):
33
"""A builder that can build a GraphIndex.
35
The resulting graph has the structure:
37
_SIGNATURE OPTIONS NODES NEWLINE
38
_SIGNATURE := 'Bazaar Graph Index 1' NEWLINE
39
OPTIONS := 'node_ref_lists=' DIGITS NEWLINE
41
NODE := KEY NULL ABSENT? NULL REFERENCES NULL VALUE NEWLINE
42
KEY := Not-whitespace-utf8
44
REFERENCES := REFERENCE_LIST (TAB REFERENCE_LIST){node_ref_lists - 1}
45
REFERENCE_LIST := (REFERENCE (CR REFERENCE)*)?
46
REFERENCE := DIGITS ; digits is the byte offset in the index of the
48
VALUE := no-newline-no-null-bytes
28
"""A builder that can build a GraphIndex."""
51
30
def __init__(self, reference_lists=0):
52
31
"""Create a GraphIndex builder.
57
36
self.reference_lists = reference_lists
60
def add_node(self, key, references, value):
61
"""Add a node to the index.
63
:param key: The key. keys must be whitespace free utf8.
64
:param references: An iterable of iterables of keys. Each is a
65
reference to another key.
66
:param value: The value to associate with the key. It may be any
67
bytes as long as it does not contain \0 or \n.
69
if not key or _whitespace_re.search(key) is not None:
70
raise errors.BadIndexKey(key)
71
if _newline_null_re.search(value) is not None:
72
raise errors.BadIndexValue(value)
73
if len(references) != self.reference_lists:
74
raise errors.BadIndexValue(references)
75
for reference_list in references:
76
for reference in reference_list:
77
if _whitespace_re.search(reference) is not None:
78
raise errors.BadIndexKey(reference)
79
if key in self._nodes:
80
raise errors.BadIndexDuplicateKey(key, self)
81
self._nodes[key] = (references, value)
84
39
lines = [_SIGNATURE]
85
40
lines.append(_OPTION_NODE_REFS + str(self.reference_lists) + '\n')
86
for key, (references, value) in sorted(self._nodes.items(),reverse=True):
87
flattened_references = []
88
for ref_list in references:
89
flattened_references.append('')
90
lines.append("%s\0\0%s\0%s\n" % (key,
91
'\t'.join(flattened_references), value))
93
42
return StringIO(''.join(lines))
96
45
class GraphIndex(object):
97
46
"""An index for data with embedded graphs.
99
The index maps keys to a list of key reference lists, and a value.
100
Each node has the same number of key reference lists. Each key reference
101
list can be empty or an arbitrary length. The value is an opaque NULL
102
terminated string without any newlines.
105
50
def __init__(self, transport, name):
144
89
signature = stream.read(len(self._signature()))
145
90
if not signature == self._signature():
146
91
raise errors.BadIndexFormatSignature(self._name, GraphIndex)
147
options_line = stream.readline()
148
if not options_line.startswith(_OPTION_NODE_REFS):
149
raise errors.BadIndexOptions(self)
151
node_ref_lists = int(options_line[len(_OPTION_NODE_REFS):-1])
153
raise errors.BadIndexOptions(self)
155
for line in stream.readlines():
159
# there must be one line - the empty trailer line.
160
raise errors.BadIndexData(self)