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."""
129
for tag, value in self.items:
130
if isinstance(value, (int, long)):
131
# must use %d so bools are written as ints
132
yield '%s %d\n' % (tag, value)
134
assert isinstance(value, (str, unicode)), ("invalid value %r" % value)
135
qv = value.replace('\\', r'\\') \
136
.replace('"', r'\"') \
137
.replace('\n', r'\n')
138
yield '%s "%s"\n' % (tag, qv)
141
"""Return stanza as a single string"""
142
return ''.join(self.to_lines())
144
def write(self, to_file):
145
"""Write stanza to a file"""
146
to_file.writelines(self.to_lines())
149
"""Return the value for a field wih given tag.
151
If there is more than one value, only the first is returned. If the
152
tag is not present, KeyError is raised.
154
for t, v in self.items:
162
def get_all(self, tag):
164
for t, v in self.items:
169
TAG_RE = re.compile(r'^[-a-zA-Z0-9_]+$')
171
return bool(TAG_RE.match(tag))
174
def read_stanza(from_lines):
175
"""Return new Stanza read from list of lines or a file"""
178
if l == None or l == '' or l == '\n':
183
assert valid_tag(tag), \
184
"invalid basic_io tag %r" % tag
186
if l[space+1] == '"':
187
# keep reading in lines, accumulating into value, until we're done
189
value = l[space+2:-2]
190
value = value.replace(r'\n', '\n') \
191
.replace(r'\"', '\"') \
192
.replace(r'\\', '\\')
194
value = int(l[space+1:])
195
items.append((tag, value))
197
return None # didn't see any content
203
############################################################
205
# XXX: Move these to object serialization code.
207
def write_revision(writer, revision):
208
s = Stanza(revision=revision.revision_id,
209
committer=revision.committer,
210
timezone=long(revision.timezone),
211
timestamp=long(revision.timestamp),
212
inventory_sha1=revision.inventory_sha1)
213
for parent_id in revision.parent_ids:
214
s.add('parent', parent_id)
215
for prop_name, prop_value in revision.properties.items():
216
s.add(prop_name, prop_value)
219
def write_inventory(writer, inventory):
220
s = Stanza(inventory_version=7)
221
writer.write_stanza(s)
223
for path, ie in inventory.iter_entries():
225
s.add(ie.kind, ie.file_id)
226
for attr in ['name', 'parent_id', 'revision',
227
'text_sha1', 'text_size', 'executable', 'symlink_target',
229
attr_val = getattr(ie, attr, None)
230
if attr == 'executable' and attr_val == 0:
232
if attr == 'parent_id' and attr_val == 'TREE_ROOT':
234
if attr_val is not None:
235
s.add(attr, attr_val)
236
writer.write_stanza(s)
239
def read_inventory(inv_file):
240
"""Read inventory object from basic_io formatted inventory file"""
241
from bzrlib.inventory import Inventory, InventoryFile
242
s = read_stanza(inv_file)
243
assert s['inventory_version'] == 7
245
for s in read_stanzas(inv_file):
246
kind, file_id = s.items[0]
249
parent_id = s['parent_id']
251
ie = InventoryFile(file_id, s['name'], parent_id)
252
ie.text_sha1 = s['text_sha1']
253
ie.text_size = s['text_size']
255
raise NotImplementedError()