## ## Copyright (c) 2006 Jason Dillon ## ## Licensed under the Apache License, Version 2.0 (the "License"); ## you may not use this file except in compliance with the License. ## You may obtain a copy of the License at ## ## http://www.apache.org/licenses/LICENSE-2.0 ## ## Unless required by applicable law or agreed to in writing, software ## distributed under the License is distributed on an "AS IS" BASIS, ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ## See the License for the specific language governing permissions and ## limitations under the License. ## ## ## $Id: //guest/jason_dillon/p4spam/main/pylib/p4spam/parser.py#2 $ $Date: 2006/04/12 $ ## import re class StreamIterator: def __init__(this, stream): this.stream = stream this.lineno = 0 this.pushline = None def pushback(this, line): this.pushline = line # this.log.debug("Pushed line: %s" % line) def __iter__(this): return this def next(this): if this.pushline != None: line = this.pushline this.pushline = None # this.log.debug("Using pushed line: %s" % line) else: line = this.stream.readline() if line == "": raise StopIteration this.lineno = this.lineno + 1 return line HEADER_MODE = 0 COMMENT_MODE = 1 AFFECTED_FILES_MODE = 2 DIFFERENCES_MODE = 3 # Change 25375 by jdillon@jdillon-sanity on 2006/04/06 14:44:22 CHANGE_HEADER_RE = re.compile(r'^Change ([0-9]+) by (\S+)@(\S+) on (\S+) (\S+)$') # ... //infrastructure/p4spam/main/pylib/p4spam/htmlmessage.py#2 edit AFFECTED_FILE_RE = re.compile(r'^\.\.\. (.+?)#([0-9]+) (\S+)$') # ==== //infrastructure/p4spam/main/pylib/p4spam/htmlmessage.py#2 (ktext) ==== DIFF_HEADER_RE = re.compile(r'^==== (.+?)#([0-9]+) \((\S+)\) ====$') AFFECTED_FILES_SECTION_PREFIX = 'Affected files ...' DIFFERENCES_SECTION_PREFIX = 'Differences ...' class DescriptionParser: def __init__(this, stream): this.stream = StreamIterator(stream) this.mode = None def parse(this): desc = Description() for line in this.stream: #this.log.debug("[%i] %s" % (this.stream.lineno, line)) # Switch modes if this.stream.lineno == 1: this.mode = HEADER_MODE elif this.stream.lineno == 2: this.mode = COMMENT_MODE line = this.stream.next() elif line.startswith(AFFECTED_FILES_SECTION_PREFIX): this.mode = AFFECTED_FILES_MODE line = this.stream.next() elif line.startswith(DIFFERENCES_SECTION_PREFIX): this.mode = DIFFERENCES_MODE # NOTE: Might not have another line to skip # Process modes if this.mode == HEADER_MODE: match = CHANGE_HEADER_RE.match(line) if match != None: desc.setHeader(match.groups()) else: raise "Unable to parse change header: %s" % line elif this.mode == COMMENT_MODE: desc.comments.append(line) elif this.mode == AFFECTED_FILES_MODE: if len(line) == 1: # print "Skip blank lines" continue match = AFFECTED_FILE_RE.match(line) if match != None: desc.files.append(AffectedFile(match.groups())) else: raise "Unable to parse: '%s'" % line elif this.mode == DIFFERENCES_MODE: desc.diffs = DifferencesIterator(this.stream) # Do not consume any more lines yet break # Pop off the last line of the comments if len(desc.comments) != 0: ## ## HACK: If nothing was processed, then comments will be empty, ## should rally have a better way to detect failure ## desc.comments.pop() return desc class Description: def __init__(this): this.change = None this.author = None this.client = None this.date = None this.time = None this.comments = [] this.files = [] this.diffs = [] def setHeader(this, groups): (this.change, this.author, this.client, this.date, this.time) = groups def __str__(this): return "%s %s@%s %s %s" % (this.change, this.author, this.client, this.date, this.time) class AffectedFile: def __init__(this, groups): (this.path, this.rev, this.action) = groups this.rev = int(this.rev) def __str__(this): return "%s#%s %s" % (this.path, this.rev, this.action) class DifferencesIterator: def __init__(this, stream): this.stream = stream this.parser = DifferenceParser(this.stream) def __iter__(this): return this def next(this): diff = this.nextDiff() if diff == None: raise StopIteration return diff def nextDiff(this): return this.parser.parse() DIFF_INFO_PREFIX = '@@' DIFF_ADDED_PREFIX = '+' DIFF_REMOVED_PREFIX = '-' class Difference: def __init__(this, groups): (this.path, this.rev, this.filetype) = groups this.rev = int(this.rev) this.lines = [] def __str__(this): return "%s#%s (%s)" % (this.path, this.rev, this.filetype) class DifferenceParser: def __init__(this, stream): this.stream = stream this.pushline = None this.pastfirst = False def parse(this): diff = None for line in this.stream: #this.log.debug("[%i] %s" % (this.stream.lineno, line)) if line.startswith('==== '): if diff == None: match = DIFF_HEADER_RE.match(line) if match != None: diff = Difference(match.groups()) # Eat the next line this.stream.next() else: # Save this line for next round and return our diff obj this.stream.pushback(line) # Pop the last line off the parsed diff diff.lines.pop() break elif diff != None: diff.lines.append(line) return diff if __name__ == '__main__': import sys parser = DescriptionParser(sys.stdin) desc = parser.parse() # Make sure there are files assert len(desc.files) != 0 # Make sure if there are any edits, that we see diffs expectDiffCount = 0 for f in desc.files: if f.action in ('edit', 'integrate'): expectDiffCount = expectDiffCount + 1 # Diffs is an iter, so parse everything first diffs = [] for d in desc.diffs: diffs.append(d) assert expectDiffCount == len(diffs) print desc print for f in desc.files: print " %s" % f print print "Comments:" for line in desc.comments: print " %s" % line, print if len(diffs) == 0: print "NO DIFFS" else: print "Diffs (%s):" % expectDiffCount for diff in diffs: print diff for line in diff.lines: print " %s" % line, print " ------ "