1
# Copyright (C) 2005 by Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""basic_io - simple text metaformat
19
The stored data consists of a series of *stanzas*, each of which contains
20
*fields* identified by an ascii name. The contents of each field can be
21
either an integer (scored in decimal) or a Unicode string.
26
# XXX: basic_io is kind of a dumb name; it seems to imply an io layer not a
29
# XXX: some redundancy is allowing to write stanzas in isolation as well as
30
# through a writer object.
32
class BasicWriter(object):
33
def __init__(self, to_file):
35
self._to_file = to_file
37
def write_stanza(self, stanza):
40
stanza.write(self._to_file)
44
class BasicReader(object):
45
"""Read stanzas from a file as a sequence
47
to_file can be anything that can be enumerated as a sequence of
48
lines (with newlines.)
50
def __init__(self, from_file):
51
self._from_file = from_file
55
s = read_stanza(self._from_file)
61
def read_stanzas(from_file):
63
s = read_stanza(from_file)
70
"""One stanza for basic_io.
72
Each stanza contains a set of named fields.
74
Names must be non-empty ascii alphanumeric plus _. Names can be repeated
75
within a stanza. Names are case-sensitive. The ordering of fields is
78
Each field value must be either an int or a string.
83
def __init__(self, **kwargs):
84
"""Construct a new Stanza.
86
The keyword arguments, if any, are added in sorted order to the stanza.
89
self.items = sorted(kwargs.items())
93
def add(self, tag, value):
94
"""Append a name and value to the stanza."""
95
## if not valid_tag(tag):
96
## raise ValueError("invalid tag %r" % tag)
97
## if not isinstance(value, (int, long, str, unicode)):
98
## raise ValueError("invalid value %r" % value)
99
self.items.append((tag, value))
101
def __contains__(self, find_tag):
102
"""True if there is any field in this stanza with the given tag."""
103
for tag, value in self.items:
109
"""Return number of pairs in the stanza."""
110
return len(self.items)
112
def __eq__(self, other):
113
if not isinstance(other, Stanza):
115
return self.items == other.items
117
def __ne__(self, other):
118
return not self.__eq__(other)
121
return "Stanza(%r)" % self.items
123
def iter_pairs(self):
124
"""Return iterator of tag, value pairs."""
125
return iter(self.items)
128
"""Generate sequence of lines for external version of this file."""
130
# max() complains if sequence is empty
132
indent = max(len(kv[0]) for kv in self.items)
133
for tag, value in self.items:
134
if isinstance(value, (int, long)):
135
# must use %d so bools are written as ints
136
yield '%*s %d\n' % (indent, tag, value)
138
assert isinstance(value, (str, unicode)), ("invalid value %r" % value)
139
qv = value.replace('\\', r'\\') \
141
yield '%*s "%s"\n' % (indent, tag, qv)
144
"""Return stanza as a single string"""
145
return ''.join(self.to_lines())
147
def write(self, to_file):
148
"""Write stanza to a file"""
149
to_file.writelines(self.to_lines())
152
"""Return the value for a field wih given tag.
154
If there is more than one value, only the first is returned. If the
155
tag is not present, KeyError is raised.
157
for t, v in self.items:
165
def get_all(self, tag):
167
for t, v in self.items:
172
TAG_RE = re.compile(r'^[-a-zA-Z0-9_]+$')
174
return bool(TAG_RE.match(tag))
177
def read_stanza(line_iter):
178
"""Return new Stanza read from list of lines or a file"""
182
if l == None or l == '':
193
raise ValueError('tag/value separator not found in line %r' % real_l)
195
assert valid_tag(tag), \
196
"invalid basic_io tag %r" % tag
198
if l[space+1] == '"':
200
valpart = l[space+2:]
202
assert valpart[-1] == '\n'
203
len_valpart = len(valpart)
204
if len_valpart >= 2 and valpart[-2] == '"':
205
# is this a real terminating doublequote, or is it escaped
206
# by a preceding backslash that is not itself escaped?
208
while i <= len_valpart and valpart[-i] == '\\':
212
# it's escaped, so the escaped backslash and newline
216
value += valpart[:-2]
221
valpart = line_iter.next()
222
except StopIteration:
223
raise ValueError('end of file in quoted string after %r' % value)
224
value = value.replace('\\"', '"').replace('\\\\', '\\')
226
value_str = l[space+1:]
228
value = int(value_str)
230
raise ValueError('invalid integer %r for tag %r in line %r'
231
% (value_str, tag, real_l))
232
items.append((tag, value))
234
return None # didn't see any content
240
############################################################
242
# XXX: Move these to object serialization code.
244
def write_revision(writer, revision):
245
s = Stanza(revision=revision.revision_id,
246
committer=revision.committer,
247
timezone=long(revision.timezone),
248
timestamp=long(revision.timestamp),
249
inventory_sha1=revision.inventory_sha1,
250
message=revision.message)
251
for parent_id in revision.parent_ids:
252
s.add('parent', parent_id)
253
for prop_name, prop_value in revision.properties.items():
254
s.add(prop_name, prop_value)
255
writer.write_stanza(s)
257
def write_inventory(writer, inventory):
258
s = Stanza(inventory_version=7)
259
writer.write_stanza(s)
261
for path, ie in inventory.iter_entries():
263
s.add(ie.kind, ie.file_id)
264
for attr in ['name', 'parent_id', 'revision',
265
'text_sha1', 'text_size', 'executable', 'symlink_target',
267
attr_val = getattr(ie, attr, None)
268
if attr == 'executable' and attr_val == 0:
270
if attr == 'parent_id' and attr_val == 'TREE_ROOT':
272
if attr_val is not None:
273
s.add(attr, attr_val)
274
writer.write_stanza(s)
277
def read_inventory(inv_file):
278
"""Read inventory object from basic_io formatted inventory file"""
279
from bzrlib.inventory import Inventory, InventoryFile
280
s = read_stanza(inv_file)
281
assert s['inventory_version'] == 7
283
for s in read_stanzas(inv_file):
284
kind, file_id = s.items[0]
287
parent_id = s['parent_id']
289
ie = InventoryFile(file_id, s['name'], parent_id)
290
ie.text_sha1 = s['text_sha1']
291
ie.text_size = s['text_size']
293
raise NotImplementedError()