From: <sv...@ww...> - 2004-06-13 17:21:33
|
Author: mkrose Date: 2004-06-13 10:21:26 -0700 (Sun, 13 Jun 2004) New Revision: 1035 Added: trunk/CSP/base/domtree.py Modified: trunk/CSP/tools/subcmd.py trunk/CSP/tools/sublib.py Log: Add a simple dom parser. Add support to subset for diffing submitted revisions. Added: trunk/CSP/base/domtree.py =================================================================== --- trunk/CSP/base/domtree.py 2004-06-13 14:37:10 UTC (rev 1034) +++ trunk/CSP/base/domtree.py 2004-06-13 17:21:26 UTC (rev 1035) @@ -0,0 +1,73 @@ +# Copyright 2004 Mark Rose <mk...@us...> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +""" +Very simple XML parsing using minidom and lots of Python magic. +""" + +from xml.dom.minidom import parse, parseString + + +class DomTree: + """ + Dom node wrapper providing access to child nodes via instance + attributes and iterators. + """ + + def __init__(self, dom): + self.name = dom.nodeName + self.type = dom.nodeType + self.node = dom + self.text = '' + self.children = [] + self._dict = {} + for node in dom.childNodes: + if node.nodeType == node.TEXT_NODE: + self.text += node.data + else: + tree = DomTree(node) + self.children.append(tree) + self._dict[tree.name] = tree + def __getattr__(self, key): + return self.__dict__['_dict'].get(key, None) + def attr(self, name, default=None): + return self.node.getAttribute(name) + def attrs(self): + return self.node._attrs.keys() + def hasattr(self, name): + return self.node.hasAttribute(name) + def __len__(self): return len(self.children) + def __str__(self): return self.__repr__() + def __repr__(self): + if self.text.strip(): return self.text + return 'node %s' % self.name + def keys(self): + return self._dict.keys() + def get(self, name, default): + return self.child + def __iter__(self): + for child in self.children: + yield child + raise StopIteration + + +def ParseFile(file): + return DomTree(parse(file)) + + +def ParseString(text): + return DomTree(parseString(text)) + Modified: trunk/CSP/tools/subcmd.py =================================================================== --- trunk/CSP/tools/subcmd.py 2004-06-13 14:37:10 UTC (rev 1034) +++ trunk/CSP/tools/subcmd.py 2004-06-13 17:21:26 UTC (rev 1035) @@ -358,10 +358,13 @@ self.save() return Result(0) + # TODO this Frankenstein method is in desparate need of refactoring def diff(self, names, revision): dircmd = os.environ.get('SUBSET_DIFF_DIR', '') onecmd = os.environ.get('SUBSET_DIFF_ONE', dircmd) + rev = 0 cs = None + info = None if not names: name = 'default changeset' files = filter(self._closed, sublib.svn_st()) @@ -385,15 +388,28 @@ files = diffs else: name = names[0] - cs = self.getChangeset(name) - if not cs: - return Error('no changeset "%s"' % name) - files = cs.files() - cs.describe() - if not files: return Result(0) + if name[0] == 'r': + try: + rev = int(name[1:]) + except ValueError: + pass + rev = abs(rev) + if rev: + name = 'revision %d' % rev + info = sublib.svn_revision_info(rev) + files = [] + else: + cs = self.getChangeset(name) + if not cs: + return Error('no changeset "%s"' % name) + files = cs.files() + cs.describe() + if not files and not rev: return Result(0) tmproot = '/tmp/subset.diff.%010d' % random.randint(1, 1000000000) os.mkdir(tmproot) cleanup = [] + if rev: + files = sublib.svn_savediffs(tmproot, rev) singleton = not cs and (len(files) == 1) if singleton and not onecmd: print 'SUBSET_DIFF_ONE undefined; cannot view diff.' @@ -416,24 +432,35 @@ index.write('<p/>\n') index.write('\n<p/>\n') else: - index.write('<h3>%s</h3>' % name) + index.write('<h3>%s</h3>\n' % name) + if info is not None: + index.write('<p/><i>Submitted by: %s<br/>\n' % info.author) + index.write('Submitted on: %s</i><p/>\n' % info.date) + index.write('%s\n' % str(info.msg).replace('\n', '<br/>\n')) cleanup.append(diffindex) if revision: index.write('<b>diff to revision %s</b>' % revision) index.write('<ul>\n') - for file in files: - if os.path.isdir(file.abspath()): - index.write('<li>%s/</li>\n' % (file.path)) - continue - outbase = file.path.replace(os.path.sep, '~') + '.diff' - outfile = os.path.join(tmproot, outbase) - cleanup.append(outfile) - exit_code = sublib.svn_savediff(file, outfile, revision) - if makeindex: - if exit_code: - index.write('<li>%s <i>...unable to diff</i></li>\n' % (file.path)) - else: - index.write('<li><a href="%s">%s</a></li>\n' % (outbase, file.path)) + if rev: + for name, path in files: + cleanup.append(path) + if makeindex: + index.write('<li><a href="%s">%s</a></li>\n' % (path, name)) + else: + for file in files: + if makeindex: + if os.path.isdir(file.abspath()): + index.write('<li>%s/</li>\n' % (file.path)) + continue + outbase = file.path.replace(os.path.sep, '~') + '.diff' + outfile = os.path.join(tmproot, outbase) + cleanup.append(outfile) + exit_code = sublib.svn_savediff(file, outfile, revision) + if makeindex: + if exit_code: + index.write('<li>%s <i>...unable to diff</i></li>\n' % (file.path)) + else: + index.write('<li><a href="%s">%s</a></li>\n' % (outbase, file.path)) if makeindex: index.write('</ul>\n</small></body></html>') index.close() @@ -943,10 +970,12 @@ def _define(self): self._long = ('diff: generate diffs for files or changesets.\n' '\n' - 'usage: %prog diff [changeset | file [file...]]' + 'usage: %prog diff [changeset | rREV | file [file...]]' '\n' 'If no arguments are specified, all files in the default changeset\n' - 'will be diffed') + 'will be diffed. The "rREV" syntax shows diffs of files submitted\n' + 'at the specified revision (e.g. "r101"), relative to the previous\n' + 'revision.') self._short = 'generate diffs' self._addKeys('diff') self._addOption('-r', '--revision', default='', metavar='REV', help='diff relative to a specific revision') Modified: trunk/CSP/tools/sublib.py =================================================================== --- trunk/CSP/tools/sublib.py 2004-06-13 14:37:10 UTC (rev 1034) +++ trunk/CSP/tools/sublib.py 2004-06-13 17:21:26 UTC (rev 1035) @@ -31,6 +31,7 @@ import time from CSP.base import app +from CSP.base import domtree class File: ADD = 'ADD' @@ -67,7 +68,7 @@ path = root else: path = ' '.join(files) - st = os.popen('svn st %s' % path).readlines() + st = os.popen('svn st -q %s' % path).readlines() files = [] for line in st: path = line[1:].strip() @@ -117,6 +118,34 @@ return exit_code +def svn_savediffs(target, rev2, rev1=None, context=100): + files = [] + if rev1 is None: + rev1 = rev2 - 1 + root = svn_root() + exit_code, out = runo('svn diff -r %d:%d --diff-cmd diff -x "-U %d -b" %s' % (rev1, rev2, context, root)) + if exit_code: return [] + diff = None + for line in out: + line = line[:-1] # remove newline + if line.startswith('Index: '): + fn = line[7:] + dest = fn.replace(os.sep, '~') + '.diff' + dest = os.path.join(target, dest) + diff = open(dest, 'wt') + files.append((fn, dest)) + if diff: diff.write(line + '\n') + return files + + +def svn_revision_info(rev): + root = svn_root() + exit_code, out = runo('svn log --xml -v -r %d %s' % (rev, root)) + if exit_code: return None + doc = domtree.ParseString(''.join(out)) + return doc.log.logentry + + def runoe(cmd): process = popen2.Popen3(cmd, capturestderr=1) process.tochild.close() |