diff options
Diffstat (limited to 'libbe')
-rw-r--r-- | libbe/bug.py | 262 | ||||
-rw-r--r-- | libbe/bugdir.py | 12 | ||||
-rw-r--r-- | libbe/comment.py | 158 | ||||
-rw-r--r-- | libbe/diff.py | 4 | ||||
-rw-r--r-- | libbe/subproc.py | 2 |
5 files changed, 358 insertions, 80 deletions
diff --git a/libbe/bug.py b/libbe/bug.py index fecb9b7..897d841 100644 --- a/libbe/bug.py +++ b/libbe/bug.py @@ -20,6 +20,7 @@ Define the Bug class for representing bugs. """ +import copy import os import os.path import errno @@ -227,7 +228,7 @@ class Bug(settings_object.SavedSettingsObject): @Property @cached_property(generator=_get_comment_root) @local_property("comment_root") - @doc_property(doc="The trunk of the comment tree") + @doc_property(doc="The trunk of the comment tree. We use a dummy root comment by default, because there can be several comment threads rooted on the same parent bug. To simplify comment interaction, we condense these threads into a single thread with a Comment dummy root.") def comment_root(): return {} def _get_vcs(self): @@ -302,7 +303,7 @@ class Bug(settings_object.SavedSettingsObject): if v is not None: lines.append(' <%s>%s</%s>' % (k,xml.sax.saxutils.escape(v),k)) for estr in self.extra_strings: - lines.append(' <extra-string>%s</extra-string>\n' % estr) + lines.append(' <extra-string>%s</extra-string>' % estr) if show_comments == True: comout = self.comment_root.xml_thread(indent=indent+2, auto_name_map=True, @@ -324,51 +325,282 @@ class Bug(settings_object.SavedSettingsObject): >>> commA = bugA.comment_root.new_reply(body='comment A') >>> commB = bugA.comment_root.new_reply(body='comment B') >>> commC = commA.new_reply(body='comment C') - >>> xml = bugA.xml(shortname="bug-1") + >>> xml = bugA.xml(shortname="bug-1", show_comments=True) >>> bugB = Bug() >>> bugB.from_xml(xml, verbose=True) - >>> bugB.xml(shortname="bug-1") == xml + >>> bugB.xml(shortname="bug-1", show_comments=True) == xml False >>> bugB.uuid = bugB.alt_id - >>> bugB.xml(shortname="bug-1") == xml + >>> for comm in bugB.comments(): + ... comm.uuid = comm.alt_id + ... comm.alt_id = None + >>> bugB.xml(shortname="bug-1", show_comments=True) == xml True + >>> bugB.explicit_attrs # doctest: +NORMALIZE_WHITESPACE + ['severity', 'status', 'creator', 'created', 'summary'] + >>> len(list(bugB.comments())) + 3 """ if type(xml_string) == types.UnicodeType: xml_string = xml_string.strip().encode('unicode_escape') - bug = ElementTree.XML(xml_string) + if hasattr(xml_string, 'getchildren'): # already an ElementTree Element + bug = xml_string + else: + bug = ElementTree.XML(xml_string) if bug.tag != 'bug': raise utility.InvalidXML( \ 'bug', bug, 'root element must be <comment>') tags=['uuid','short-name','severity','status','assigned','target', - 'reporter', 'creator', 'created', 'summary', 'extra-string', - 'comment'] + 'reporter', 'creator','created','summary','extra-string'] + self.explicit_attrs = [] uuid = None estrs = [] + comments = [] for child in bug.getchildren(): if child.tag == 'short-name': pass + elif child.tag == 'comment': + comm = comment.Comment(bug=self) + comm.from_xml(child) + comments.append(comm) + continue elif child.tag in tags: if child.text == None or len(child.text) == 0: text = settings_object.EMPTY else: text = xml.sax.saxutils.unescape(child.text) text = text.decode('unicode_escape').strip() - if child.tag == "uuid": + if child.tag == 'uuid': uuid = text continue # don't set the bug's uuid tag. - if child.tag == 'extra-string': + elif child.tag == 'extra-string': estrs.append(text) continue # don't set the bug's extra_string yet. - else: - attr_name = child.tag.replace('-','_') + attr_name = child.tag.replace('-','_') + self.explicit_attrs.append(attr_name) setattr(self, attr_name, text) elif verbose == True: print >> sys.stderr, "Ignoring unknown tag %s in %s" \ % (child.tag, comment.tag) - if uuid not in [None, self.uuid]: + if uuid != self.uuid: if not hasattr(self, 'alt_id') or self.alt_id == None: self.alt_id = uuid self.extra_strings = estrs + self.add_comments(comments) + + def add_comment(self, comment, *args, **kwargs): + """ + Add a comment too the current bug, under the parent specified + by comment.in_reply_to. + Note: If a bug uuid is given, set .alt_id to it's value. + >>> bugA = Bug(uuid='0123', summary='Need to test Bug.add_comment()') + >>> bugA.creator = 'Jack' + >>> commA = bugA.comment_root.new_reply(body='comment A') + >>> commA.uuid = 'commA' + >>> commB = comment.Comment(body='comment B') + >>> commB.uuid = 'commB' + >>> bugA.add_comment(commB) + >>> commC = comment.Comment(body='comment C') + >>> commC.uuid = 'commC' + >>> commC.in_reply_to = commA.uuid + >>> bugA.add_comment(commC) + >>> print bugA.xml(shortname="bug-1", show_comments=True) # doctest: +ELLIPSIS + <bug> + <uuid>0123</uuid> + <short-name>bug-1</short-name> + <severity>minor</severity> + <status>open</status> + <creator>Jack</creator> + <created>...</created> + <summary>Need to test Bug.add_comment()</summary> + <comment> + <uuid>commA</uuid> + <short-name>bug-1:1</short-name> + <author></author> + <date>...</date> + <content-type>text/plain</content-type> + <body>comment A</body> + </comment> + <comment> + <uuid>commC</uuid> + <short-name>bug-1:2</short-name> + <in-reply-to>commA</in-reply-to> + <author></author> + <date>...</date> + <content-type>text/plain</content-type> + <body>comment C</body> + </comment> + <comment> + <uuid>commB</uuid> + <short-name>bug-1:3</short-name> + <author></author> + <date>...</date> + <content-type>text/plain</content-type> + <body>comment B</body> + </comment> + </bug> + """ + self.add_comments([comment], **kwargs) + + def add_comments(self, comments, default_parent=None, + ignore_missing_references=False): + """ + Convert a raw list of comments to single root comment. If a + comment does not specify a parent with .in_reply_to, the + parent defaults to .comment_root, but you can specify another + default parent via default_parent. + """ + uuid_map = {} + if default_parent == None: + default_parent = self.comment_root + for c in list(self.comments()) + comments: + assert c.uuid != None + assert c.uuid not in uuid_map + uuid_map[c.uuid] = c + if c.alt_id != None: + uuid_map[c.alt_id] = c + uuid_map[None] = self.comment_root + if default_parent != self.comment_root: + assert default_parent.uuid in uuid_map, default_parent + for c in comments: + if c.in_reply_to == None \ + and default_parent.uuid != comment.INVALID_UUID: + c.in_reply_to = default_parent.uuid + elif c.in_reply_to == comment.INVALID_UUID: + c.in_reply_to = None + try: + parent = uuid_map[c.in_reply_to] + except KeyError: + if ignore_missing_references == True: + print >> sys.stderr, \ + "Ignoring missing reference to %s" % c.in_reply_to + parent = default_parent + if parent.uuid != comment.INVALID_UUID: + c.in_reply_to = parent.uuid + else: + raise comment.MissingReference(c) + c.bug = self + parent.append(c) + + def merge(self, other, accept_changes=True, + accept_extra_strings=True, accept_comments=True, + change_exception=False): + """ + Merge info from other into this bug. Overrides any attributes + in self that are listed in other.explicit_attrs. + >>> bugA = Bug(uuid='0123', summary='Need to test Bug.merge()') + >>> bugA.date = 'Thu, 01 Jan 1970 00:00:00 +0000' + >>> bugA.creator = 'Frank' + >>> bugA.extra_strings += ['TAG: very helpful'] + >>> bugA.extra_strings += ['TAG: favorite'] + >>> commA = bugA.comment_root.new_reply(body='comment A') + >>> commA.uuid = 'uuid-commA' + >>> bugB = Bug(uuid='3210', summary='More tests for Bug.merge()') + >>> bugB.date = 'Fri, 02 Jan 1970 00:00:00 +0000' + >>> bugB.creator = 'John' + >>> bugB.explicit_attrs = ['creator', 'summary'] + >>> bugB.extra_strings += ['TAG: very helpful'] + >>> bugB.extra_strings += ['TAG: useful'] + >>> commB = bugB.comment_root.new_reply(body='comment B') + >>> commB.uuid = 'uuid-commB' + >>> bugA.merge(bugB, accept_changes=False, accept_extra_strings=False, + ... accept_comments=False, change_exception=False) + >>> print bugA.creator + Frank + >>> bugA.merge(bugB, accept_changes=False, accept_extra_strings=False, + ... accept_comments=False, change_exception=True) + Traceback (most recent call last): + ... + ValueError: Merge would change creator "Frank"->"John" for bug 0123 + >>> print bugA.creator + Frank + >>> bugA.merge(bugB, accept_changes=True, accept_extra_strings=False, + ... accept_comments=False, change_exception=True) + Traceback (most recent call last): + ... + ValueError: Merge would add extra string "TAG: useful" for bug 0123 + >>> print bugA.creator + John + >>> print bugA.extra_strings + ['TAG: favorite', 'TAG: very helpful'] + >>> bugA.merge(bugB, accept_changes=True, accept_extra_strings=True, + ... accept_comments=False, change_exception=True) + Traceback (most recent call last): + ... + ValueError: Merge would add comment uuid-commB (alt: None) to bug 0123 + >>> print bugA.extra_strings + ['TAG: favorite', 'TAG: useful', 'TAG: very helpful'] + >>> bugA.merge(bugB, accept_changes=True, accept_extra_strings=True, + ... accept_comments=True, change_exception=True) + >>> print bugA.xml(show_comments=True) # doctest: +ELLIPSIS + <bug> + <uuid>0123</uuid> + <short-name>0123</short-name> + <severity>minor</severity> + <status>open</status> + <creator>John</creator> + <created>...</created> + <summary>More tests for Bug.merge()</summary> + <extra-string>TAG: favorite</extra-string> + <extra-string>TAG: useful</extra-string> + <extra-string>TAG: very helpful</extra-string> + <comment> + <uuid>uuid-commA</uuid> + <short-name>0123:1</short-name> + <author></author> + <date>...</date> + <content-type>text/plain</content-type> + <body>comment A</body> + </comment> + <comment> + <uuid>uuid-commB</uuid> + <short-name>0123:2</short-name> + <author></author> + <date>...</date> + <content-type>text/plain</content-type> + <body>comment B</body> + </comment> + </bug> + """ + for attr in other.explicit_attrs: + old = getattr(self, attr) + new = getattr(other, attr) + if old != new: + if accept_changes == True: + setattr(self, attr, new) + elif change_exception == True: + raise ValueError, \ + 'Merge would change %s "%s"->"%s" for bug %s' \ + % (attr, old, new, self.uuid) + for estr in other.extra_strings: + if not estr in self.extra_strings: + if accept_extra_strings == True: + self.extra_strings.append(estr) + elif change_exception == True: + raise ValueError, \ + 'Merge would add extra string "%s" for bug %s' \ + % (estr, self.uuid) + for o_comm in other.comments(): + try: + s_comm = self.comment_root.comment_from_uuid(o_comm.uuid) + except KeyError, e: + try: + s_comm = self.comment_root.comment_from_uuid(o_comm.alt_id) + except KeyError, e: + s_comm = None + if s_comm == None: + if accept_comments == True: + o_comm_copy = copy.copy(o_comm) + o_comm_copy.bug = self + self.comment_root.add_reply(o_comm_copy) + elif change_exception == True: + raise ValueError, \ + 'Merge would add comment %s (alt: %s) to bug %s' \ + % (o_comm.uuid, o_comm.alt_id, self.uuid) + else: + s_comm.merge(o_comm, accept_changes=accept_changes, + accept_extra_strings=accept_extra_strings, + change_exception=change_exception) def string(self, shortlist=False, show_comments=False): if self.bugdir == None: @@ -493,8 +725,8 @@ class Bug(settings_object.SavedSettingsObject): return self.comment_root.comment_from_shortname(shortname, *args, **kwargs) - def comment_from_uuid(self, uuid): - return self.comment_root.comment_from_uuid(uuid) + def comment_from_uuid(self, uuid, *args, **kwargs): + return self.comment_root.comment_from_uuid(uuid, *args, **kwargs) def comment_shortnames(self, shortname=None): """ diff --git a/libbe/bugdir.py b/libbe/bugdir.py index 675b744..301ceb6 100644 --- a/libbe/bugdir.py +++ b/libbe/bugdir.py @@ -115,7 +115,7 @@ class BugDir (list, settings_object.SavedSettingsObject): all bugs/comments/etc. that have been loaded into memory. If you've been living in memory and want to move to .sync_with_disk==True, but you're not sure if anything has been - changed in memory, a call to save() immediately before the + changed in memory, a call to .save() immediately before the .set_sync_with_disk(True) call is a safe move. Regardless of .sync_with_disk, a call to .save() will write out @@ -239,7 +239,7 @@ settings easy. Don't set this attribute. Set .vcs instead, and map = {} for bug in self: map[bug.uuid] = bug - for uuid in self.list_uuids(): + for uuid in self.uuids(): if uuid not in map: map[uuid] = None self._bug_map_value = map # ._bug_map_value used by @local_property @@ -483,7 +483,7 @@ settings easy. Don't set this attribute. Set .vcs instead, and if self.sync_with_disk == False: raise DiskAccessRequired("load all bugs") self._clear_bugs() - for uuid in self.list_uuids(): + for uuid in self.uuids(): self._load_bug(uuid) def save(self): @@ -550,7 +550,7 @@ settings easy. Don't set this attribute. Set .vcs instead, and # methods for managing bugs - def list_uuids(self): + def uuids(self): uuids = [] if self.sync_with_disk == True and os.path.exists(self.get_path()): # list the uuids on disk @@ -651,7 +651,7 @@ class SimpleBugDir (BugDir): """ For testing. Set sync_with_disk==False for a memory-only bugdir. >>> bugdir = SimpleBugDir() - >>> uuids = list(bugdir.list_uuids()) + >>> uuids = list(bugdir.uuids()) >>> uuids.sort() >>> print uuids ['a', 'b'] @@ -741,7 +741,7 @@ class BugDirTestCase(unittest.TestCase): self.bugdir.new_bug(uuid="c", summary="Praying mantis") length = len(self.bugdir) self.failUnless(length == 3, "%d != 3 bugs" % length) - uuids = list(self.bugdir.list_uuids()) + uuids = list(self.bugdir.uuids()) self.failUnless(len(uuids) == 3, "%d != 3 uuids" % len(uuids)) self.failUnless(uuids == ["a","b","c"], str(uuids)) bugA = self.bugdir.bug_from_uuid("a") diff --git a/libbe/comment.py b/libbe/comment.py index 5cc43c4..c5f1cc9 100644 --- a/libbe/comment.py +++ b/libbe/comment.py @@ -65,53 +65,6 @@ class DiskAccessRequired (Exception): INVALID_UUID = "!!~~\n INVALID-UUID \n~~!!" -def list_to_root(comments, bug, root=None, - ignore_missing_references=False): - """ - Convert a raw list of comments to single root comment. We use a - dummy root comment by default, because there can be several - comment threads rooted on the same parent bug. To simplify - comment interaction, we condense these threads into a single - thread with a Comment dummy root. Can also be used to append - a list of subcomments to a non-dummy root comment, so long as - all the new comments are descendants of the root comment. - - No Comment method should use the dummy comment. - """ - root_comments = [] - uuid_map = {} - for comment in comments: - assert comment.uuid != None - uuid_map[comment.uuid] = comment - for comment in comments: - if comment.alt_id != None and comment.alt_id not in uuid_map: - uuid_map[comment.alt_id] = comment - if root == None: - root = Comment(bug, uuid=INVALID_UUID) - else: - uuid_map[root.uuid] = root - for comm in comments: - if comm.in_reply_to == INVALID_UUID: - comm.in_reply_to = None - rep = comm.in_reply_to - if rep == None or rep == bug.uuid: - root_comments.append(comm) - else: - parentUUID = comm.in_reply_to - try: - parent = uuid_map[parentUUID] - parent.add_reply(comm) - except KeyError, e: - if ignore_missing_references == True: - print >> sys.stderr, \ - "Ignoring missing reference to %s" % parentUUID - comm.in_reply_to = None - root_comments.append(comm) - else: - raise MissingReference(comm) - root.extend(root_comments) - return root - def loadComments(bug, load_full=False): """ Set load_full=True when you want to load the comment completely @@ -132,7 +85,9 @@ def loadComments(bug, load_full=False): comm.load_settings() dummy = comm.body # force the body to load comments.append(comm) - return list_to_root(comments, bug) + bug.comment_root = Comment(bug, uuid=INVALID_UUID) + bug.add_comments(comments) + return bug.comment_root def saveComments(bug): if bug.sync_with_disk == False: @@ -344,7 +299,7 @@ class Comment(Tree, settings_object.SavedSettingsObject): if v != None: lines.append(' <%s>%s</%s>' % (k,xml.sax.saxutils.escape(v),k)) for estr in self.extra_strings: - lines.append(' <extra-string>%s</extra-string>\n' % estr) + lines.append(' <extra-string>%s</extra-string>' % estr) lines.append('</comment>') istring = ' '*indent sep = '\n' + istring @@ -362,6 +317,8 @@ class Comment(Tree, settings_object.SavedSettingsObject): >>> xml = commA.xml(shortname="com-1") >>> commB = Comment() >>> commB.from_xml(xml, verbose=True) + >>> commB.explicit_attrs + ['author', 'date', 'content_type', 'body', 'alt_id'] >>> commB.xml(shortname="com-1") == xml False >>> commB.uuid = commB.alt_id @@ -371,12 +328,16 @@ class Comment(Tree, settings_object.SavedSettingsObject): """ if type(xml_string) == types.UnicodeType: xml_string = xml_string.strip().encode('unicode_escape') - comment = ElementTree.XML(xml_string) + if hasattr(xml_string, 'getchildren'): # already an ElementTree Element + comment = xml_string + else: + comment = ElementTree.XML(xml_string) if comment.tag != 'comment': raise utility.InvalidXML( \ 'comment', comment, 'root element must be <comment>') tags=['uuid','alt-id','in-reply-to','author','date','content-type', 'body','extra-string'] + self.explicit_attrs = [] uuid = None body = None estrs = [] @@ -392,19 +353,21 @@ class Comment(Tree, settings_object.SavedSettingsObject): if child.tag == 'uuid': uuid = text continue # don't set the comment's uuid tag. - if child.tag == 'body': + elif child.tag == 'body': body = text + self.explicit_attrs.append(child.tag) continue # don't set the comment's body yet. - if child.tag == 'extra-string': + elif child.tag == 'extra-string': estrs.append(text) continue # don't set the comment's extra_string yet. - else: - attr_name = child.tag.replace('-','_') + attr_name = child.tag.replace('-','_') + self.explicit_attrs.append(attr_name) setattr(self, attr_name, text) elif verbose == True: print >> sys.stderr, 'Ignoring unknown tag %s in %s' \ % (child.tag, comment.tag) - if self.alt_id == None and uuid not in [None, self.uuid]: + if uuid != self.uuid and self.alt_id == None: + self.explicit_attrs.append('alt_id') self.alt_id = uuid if body != None: if self.content_type.startswith('text/'): @@ -413,6 +376,78 @@ class Comment(Tree, settings_object.SavedSettingsObject): self.body = base64.decodestring(body) self.extra_strings = estrs + def merge(self, other, accept_changes=True, + accept_extra_strings=True, change_exception=False): + """ + Merge info from other into this comment. Overrides any + attributes in self that are listed in other.explicit_attrs. + >>> commA = Comment(bug=None, body='Some insightful remarks') + >>> commA.uuid = '0123' + >>> commA.date = 'Thu, 01 Jan 1970 00:00:00 +0000' + >>> commA.author = 'Frank' + >>> commA.extra_strings += ['TAG: very helpful'] + >>> commA.extra_strings += ['TAG: favorite'] + >>> commB = Comment(bug=None, body='More insightful remarks') + >>> commB.uuid = '3210' + >>> commB.date = 'Fri, 02 Jan 1970 00:00:00 +0000' + >>> commB.author = 'John' + >>> commB.explicit_attrs = ['author', 'body'] + >>> commB.extra_strings += ['TAG: very helpful'] + >>> commB.extra_strings += ['TAG: useful'] + >>> commA.merge(commB, accept_changes=False, + ... accept_extra_strings=False, change_exception=False) + >>> commA.merge(commB, accept_changes=False, + ... accept_extra_strings=False, change_exception=True) + Traceback (most recent call last): + ... + ValueError: Merge would change author "Frank"->"John" for comment 0123 + >>> commA.merge(commB, accept_changes=True, + ... accept_extra_strings=False, change_exception=True) + Traceback (most recent call last): + ... + ValueError: Merge would add extra string "TAG: useful" to comment 0123 + >>> print commA.author + John + >>> print commA.extra_strings + ['TAG: favorite', 'TAG: very helpful'] + >>> commA.merge(commB, accept_changes=True, + ... accept_extra_strings=True, change_exception=True) + >>> print commA.extra_strings + ['TAG: favorite', 'TAG: useful', 'TAG: very helpful'] + >>> print commA.xml() + <comment> + <uuid>0123</uuid> + <short-name>0123</short-name> + <author>John</author> + <date>Thu, 01 Jan 1970 00:00:00 +0000</date> + <content-type>text/plain</content-type> + <body>More insightful remarks</body> + <extra-string>TAG: favorite</extra-string> + <extra-string>TAG: useful</extra-string> + <extra-string>TAG: very helpful</extra-string> + </comment> + """ + for attr in other.explicit_attrs: + old = getattr(self, attr) + new = getattr(other, attr) + if old != new: + if accept_changes == True: + setattr(self, attr, new) + elif change_exception == True: + raise ValueError, \ + 'Merge would change %s "%s"->"%s" for comment %s' \ + % (attr, old, new, self.uuid) + if self.alt_id == self.uuid: + self.alt_id = None + for estr in other.extra_strings: + if not estr in self.extra_strings: + if accept_extra_strings == True: + self.extra_strings.append(estr) + elif change_exception == True: + raise ValueError, \ + 'Merge would add extra string "%s" to comment %s' \ + % (estr, self.uuid) + def string(self, indent=0, shortname=None): """ >>> comm = Comment(bug=None, body="Some\\ninsightful\\nremarks\\n") @@ -674,7 +709,7 @@ class Comment(Tree, settings_object.SavedSettingsObject): raise InvalidShortname(comment_shortname, list(self.comment_shortnames(*args, **kwargs))) - def comment_from_uuid(self, uuid): + def comment_from_uuid(self, uuid, match_alt_id=True): """ Use a comment shortname to look up a comment. >>> a = Comment(bug=None, uuid="a") @@ -684,13 +719,24 @@ class Comment(Tree, settings_object.SavedSettingsObject): >>> c.uuid = "c" >>> d = a.new_reply() >>> d.uuid = "d" + >>> d.alt_id = "d-alt" >>> comm = a.comment_from_uuid("d") >>> id(comm) == id(d) True + >>> comm = a.comment_from_uuid("d-alt") + >>> id(comm) == id(d) + True + >>> comm = a.comment_from_uuid(None, match_alt_id=False) + Traceback (most recent call last): + ... + KeyError: None """ for comment in self.traverse(): if comment.uuid == uuid: return comment + if match_alt_id == True and uuid != None \ + and comment.alt_id == uuid: + return comment raise KeyError(uuid) def cmp_attr(comment_1, comment_2, attr, invert=False): diff --git a/libbe/diff.py b/libbe/diff.py index cce3b0f..6e830c6 100644 --- a/libbe/diff.py +++ b/libbe/diff.py @@ -207,7 +207,7 @@ class Diff (object): added = [] removed = [] modified = [] - for uuid in self.new_bugdir.list_uuids(): + for uuid in self.new_bugdir.uuids(): new_bug = self.new_bugdir.bug_from_uuid(uuid) try: old_bug = self.old_bugdir.bug_from_uuid(uuid) @@ -220,7 +220,7 @@ class Diff (object): new_bug.load_comments() if old_bug != new_bug: modified.append((old_bug, new_bug)) - for uuid in self.old_bugdir.list_uuids(): + for uuid in self.old_bugdir.uuids(): if not self.new_bugdir.has_bug(uuid): old_bug = self.old_bugdir.bug_from_uuid(uuid) removed.append(old_bug) diff --git a/libbe/subproc.py b/libbe/subproc.py index 3e58271..fe88206 100644 --- a/libbe/subproc.py +++ b/libbe/subproc.py @@ -96,7 +96,7 @@ class Pipe (object): >>> p.statuses [1, 0] >>> p.stderrs # doctest: +ELLIPSIS - ["find: `...': Permission denied\\n...", ''] + [...find: ...: Permission denied..., ''] """ def __init__(self, cmds, stdin=None): # spawn processes |