1
# Copyright (C) 2005 by Canonical Ltd
3
# Distributed under the GNU General Public Licence v2
5
# \subsection{\emph{rio} - simple text metaformat}
7
# \emph{r} stands for `restricted', `reproducible', or `rfc822-like'.
9
# The stored data consists of a series of \emph{stanzas}, each of which contains
10
# \emph{fields} identified by an ascii name, with Unicode or string contents.
11
# The field tag is constrained to alphanumeric characters.
12
# There may be more than one field in a stanza with the same name.
14
# The format itself does not deal with character encoding issues, though
15
# the result will normally be written in Unicode.
17
# The format is intended to be simple enough that there is exactly one character
18
# stream representation of an object and vice versa, and that this relation
19
# will continue to hold for future versions of bzr.
21
# In comments, $\min(1,10)$
27
# XXX: some redundancy is allowing to write stanzas in isolation as well as
28
# through a writer object.
30
class RioWriter(object):
31
def __init__(self, to_file):
33
self._to_file = to_file
35
def write_stanza(self, stanza):
38
stanza.write(self._to_file)
42
class RioReader(object):
43
"""Read stanzas from a file as a sequence
45
to_file can be anything that can be enumerated as a sequence of
46
lines (with newlines.)
48
def __init__(self, from_file):
49
self._from_file = from_file
53
s = read_stanza(self._from_file)
59
def read_stanzas(from_file):
61
s = read_stanza(from_file)
68
"""One stanza for rio.
70
Each stanza contains a set of named fields.
72
Names must be non-empty ascii alphanumeric plus _. Names can be repeated
73
within a stanza. Names are case-sensitive. The ordering of fields is
76
Each field value must be either an int or a string.
81
def __init__(self, **kwargs):
82
"""Construct a new Stanza.
84
The keyword arguments, if any, are added in sorted order to the stanza.
88
for tag, value in sorted(kwargs.items()):
91
def add(self, tag, value):
92
"""Append a name and value to the stanza."""
93
assert valid_tag(tag), \
94
("invalid tag %r" % tag)
95
if isinstance(value, (str, unicode)):
97
## elif isinstance(value, (int, long)):
98
## value = str(value) # XXX: python2.4 without L-suffix
100
raise ValueError("invalid value %r" % value)
101
self.items.append((tag, value))
103
def __contains__(self, find_tag):
104
"""True if there is any field in this stanza with the given tag."""
105
for tag, value in self.items:
111
"""Return number of pairs in the stanza."""
112
return len(self.items)
114
def __eq__(self, other):
115
if not isinstance(other, Stanza):
117
return self.items == other.items
119
def __ne__(self, other):
120
return not self.__eq__(other)
123
return "Stanza(%r)" % self.items
125
def iter_pairs(self):
126
"""Return iterator of tag, value pairs."""
127
return iter(self.items)
130
"""Generate sequence of lines for external version of this file."""
132
# max() complains if sequence is empty
135
for tag, value in self.items:
136
assert isinstance(value, (str, unicode))
138
result.append(tag + ': \n')
140
val_lines = value.splitlines()
141
result.append(tag + ': ' + val_lines[0] + '\n')
142
for line in val_lines[1:]:
143
result.append('\t' + line + '\n')
145
result.append(tag + ': ' + value + '\n')
149
"""Return stanza as a single string"""
150
return ''.join(self.to_lines())
152
def write(self, to_file):
153
"""Write stanza to a file"""
154
to_file.writelines(self.to_lines())
157
"""Return the value for a field wih given tag.
159
If there is more than one value, only the first is returned. If the
160
tag is not present, KeyError is raised.
162
for t, v in self.items:
170
def get_all(self, tag):
172
for t, v in self.items:
177
_tag_re = re.compile(r'^[-a-zA-Z0-9_]+$')
179
return bool(_tag_re.match(tag))
182
def read_stanza(line_iter):
183
"""Return new Stanza read from list of lines or a file
185
Returns one Stanza that was read, or returns None at end of file. If a
186
blank line follows the stanza, it is consumed. It's not an error for
187
there to be no blank at end of file. If there is a blank file at the
188
start of the input this is really an empty stanza and that is returned.
193
for line in line_iter:
194
if line == None or line == '':
198
break # end of stanza
199
assert line[-1] == '\n'
203
colon_index = line.index(': ')
205
raise ValueError('tag/value separator not found in line %r' % real_l)
206
tag = line[:colon_index]
207
assert valid_tag(tag), \
208
"invalid rio tag %r" % tag
209
value_start = line[colon_index+2:-1]
210
# TODO: Handle multiline values
212
stanza.add(tag, value)
214
return None # didn't see any content