1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 """
20 Internal module providing output parsing functionality.
21
22 This module implements some of public interface for the
23 pybaz_ package. But for convenience reasons the author prefers
24 to store this code in a file separate from ``__init__.py``.
25
26 .. _pybaz: arch-module.html
27
28 This module is strictly internal and should never be used.
29 """
30
31 __all__ = []
32
33 from pathname import *
34 from _escaping import *
35
36
37 __all__.extend((
38 'Chatter',
39 'classify_chatter',
40 ))
41
43 """Chatter lines in ``tla`` output.
44
45 :ivar text: chatter text without the ``"* "`` prefix.
46 :type text: str
47 """
48
50 """Create a chatter item.
51
52 :param text: tla-style chatter line, starting with ``"* "``
53 :type text: str
54 """
55 assert text[:2] == '* '
56 self.text = text[2:]
57
59 """tla-style chatter line with ``"* "`` prefix."""
60 return "* " + self.text
61
62
64 """Classify chatter in a sequence of strings.
65
66 Generator that yields Chatter objects for chatter lines, and
67 yields other lines verbatim.
68
69 :param iter: iterable of str
70 :rtype: iterable of `Chatter` or str
71 """
72 for line in iter:
73 if line.startswith("* "):
74 yield Chatter(line)
75 else:
76 yield line
77
78
79 __all__.extend((
80 'MergeOutcome',
81 'TreeChange',
82 ))
83
85 """Abstract base class for changeset application summary output lines.
86
87 :ivar name: name of the corresponding file
88 :type name: `FileName` or `DirName`
89 :ivar directory: does the change applies to a directory?
90 :type directory: bool
91 """
92
93 - def _parse(self, text, pad, filepre, dirpre=None):
94 self._pad = pad
95 assert 2 == len(filepre)
96 assert dirpre is None or 2 == len(dirpre)
97 prefix = text[0:2+len(pad)]
98 if prefix == filepre+pad:
99 self._directory = False
100 elif dirpre is not None and prefix == dirpre+pad:
101 self._directory = True
102 else:
103 raise AssertionError('bad prefix: %r' % prefix)
104 name = name_unescape(text[len(prefix):])
105 self._name = (FileName, DirName)[self._directory](name)
106
108 """tla-style changeset application line."""
109 raise NotImplementedError
110
111 - def _to_str(self, filepre, dirpre=None):
112 if self._directory:
113 return ''.join((dirpre, self._pad, name_escape(self.name)))
114 else:
115 return ''.join((filepre, self._pad, name_escape(self.name)))
116
118 return self._directory
119 directory = property(_get_directory)
120
123 name = property(_get_name)
124
125
127 """Abstract base class for ``changes`` summary output lines."""
128
130 """tla-style change line,"""
131 raise NotImplementedError
132
133
134 __all__.extend((
135 'FileAddition',
136 'FileDeletion',
137 'FileModification',
138 'FilePermissionsChange',
139 'FileRename',
140 'SymlinkModification',
141 ))
142
144 """Changeset summary line for a new file.
145
146 :ivar name: name of the added file or directory.
147 :ivar directory: is ``name`` a directory name?
148 """
149
151 self._parse(text, pad, 'A ', 'A/')
152
154 return self._to_str('A ', 'A/')
155
156
158 """Changeset summary line for a deleted file.
159
160 :ivar name: name of the deleted file or directory.
161 :ivar directory: is ``name`` a directory name?
162 """
163
165 self._parse(text, pad, 'D ', 'D/')
166
168 return self._to_str('D ', 'D/')
169
170
172 """Changeset summary line for file whose contents were modified.
173
174 :ivar name: name of the modified file.
175 :ivar binary: is ``name`` a binary file?
176 :type binary: bool
177 :ivar directory: always False
178 """
179
181 self._directory = False
182 self._pad = pad
183 prefix = text[0:2+len(pad)]
184 if prefix == 'M '+pad: self.binary = False
185 elif prefix == 'Mb'+pad: self.binary = True
186 else: raise AssertionError('bad prefix: %r' % prefix)
187 name = name_unescape(text[len(prefix):])
188 self._name = FileName(name)
189
195
196
198 """Changeset summary line for a change in permissions.
199
200 :ivar name: name of the file or directory whose permissions changed.
201 :ivar directory: is ``name`` a directory name?
202 """
203
205 if text.startswith('--/ '):
206 self._patching = True
207 self._directory = True
208 name = name_unescape(text[len('--/ '):])
209 self._name = DirName(name)
210 else:
211 self._patching = False
212 self._parse(text, pad, '--', '-/')
213
215 if self._patching:
216 assert self._directory
217 return '--/ ' + name_escape(self.name)
218 else:
219 return self._to_str('--', '-/')
220
221
223 """Changeset summary line for a renaming.
224
225 :ivar name: new name of the moved file or directory.
226 :ivar oldname: old name of the moved file or directory.
227 :type oldname: `FileName`, `DirName`
228 :ivar directory: are ``name`` and ``oldname`` directory names?
229 """
230
232 self._pad = pad
233 prefix = text[0:2+len(pad)]
234 if prefix == '=>'+pad:
235 self._directory = False
236 elif prefix == '/>'+pad:
237 self._directory = True
238 else:
239 raise AssertionError('bad prefix: %r' % prefix)
240 oldnew = text[len(prefix):].split('\t')
241 assert len(oldnew) == 2
242 ctor = (FileName, DirName)[self.directory]
243 self.oldname, self._name = map(ctor, map(name_unescape, oldnew))
244
246 if self.directory:
247 format = '/>%s%s\t%s'
248 else:
249 format = '=>%s%s\t%s'
250 names = map(name_escape, (self.oldname, self.name))
251 return format % ((self._pad,) + tuple(names))
252
253
255 """Changeset summary line for a symlink modification.
256
257 :ivar name: name of the modified symbolic link.
258 :ivar directory: always False
259 """
260
262 self._parse(text, pad, '->')
263
265 return self._to_str('->')
266
267
268 __all__.extend((
269 'classify_changeset_creation',
270 'PatchConflict',
271 'classify_changeset_application',
272 ))
273
275 """Classify the output of a changeset creation command.
276
277 :param lines: incremental output from a changeset creation command.
278 :type lines: iterator of str
279 :param pad: padding text between prefix and file name.
280 :type pad: str
281 :rtype: Iterable of `Chatter`, `TreeChange`, str
282
283 :note: diff output (*e.g.* ``changes --diffs``) is not supported.
284 """
285 for line in classify_chatter(lines):
286 if isinstance(line, Chatter) or len(line) < 2:
287 yield line
288 elif line[:3] in ('A ', 'A/ '):
289 yield FileAddition(line, pad)
290 elif line[:3] in ('D ', 'D/ '):
291 yield FileDeletion(line, pad)
292 elif line[:3] in ('M ', 'Mb '):
293 yield FileModification(line, pad)
294 elif line[:3] in ('-- ', '-/ '):
295 yield FilePermissionsChange(line, pad)
296 elif line[:3] in ('=> ', '/> '):
297 yield FileRename(line, pad)
298 elif line[:3] == '-> ':
299 yield SymlinkModification(line, pad)
300 else:
301 yield line
302
303
305 """Changeset application summary line for a patch conflict.
306
307 :ivar name: name of the file where the patch conflict occurred.
308 :ivar directory: always False
309 """
311 self._parse(text, ' ', 'C ')
312
314 return self._to_str('C ')
315
316
318 """Classify the output of a change-producing command.
319
320 :param lines: incremental output from a changeset application command.
321 :type lines: iterable of str
322 :rtype: iterable of `Chatter`, `MergeOutcome`, str
323
324 :note: diff output (*e.g.* ``changes --diffs``) is not supported.
325 """
326 for line in classify_changeset_creation(lines, pad=' '):
327 if isinstance(line, (Chatter, TreeChange)) or len(line) < 2:
328 yield line
329 elif line[:4] == '--/ ':
330 yield FilePermissionsChange(line, pad=' ')
331 elif line[:4] == 'C ':
332 yield PatchConflict(line)
333 else:
334 yield line
335