#!/usr/bin/env python # Copyright (C) 2001-2023 Artifex Software, Inc. # All Rights Reserved. # # This software is provided AS-IS with no warranty, either express or # implied. # # This software is distributed under license and may not be copied, # modified or distributed except as expressly authorized under the terms # of the license contained in the file LICENSE in this distribution. # # Refer to licensing information at http://www.artifex.com or contact # Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, # CA 94129, USA, for further information. # # script to generate the split Changes/Details html changelogs # for Ghostscript from the output of 'svn log --xml' import string, re import xml.parsers.expat import sys, codecs, os def html_escape(string): table = { '&': '&', '"': '"', '>': '>', '<': '<', '$': '$'} new = [] for char in string: new.append(table.get(char,char)) return "".join(new) class Entry: '''a class representing a single changelog entry''' data = {} has_details = False has_differences = False r = re.compile('^[\[ ]*DETAILS[ :\]]*$', re.I) c = re.compile('^[ ]*EXPECTED DIFFERENCES[ :]*$', re.I) d = re.compile('^[ ]*DIFFERENCES[ :]*$', re.I) codec = codecs.getencoder("utf8") def reset(self): self.data = {} self.has_details = False self.has_differences = False def add(self, key, value): if not self.data.has_key(key): self.data[key] = value else: self.data[key] = string.join((self.data[key], value)) def listadd(self, key, value): if not self.data.has_key(key): self.data[key] = [value] else: self.data[key].append(value) def addmsg(self, value): if not self.data.has_key('msg'): self.data['msg'] = [] self.data['msg'].append(value) if self.r.search(value): self.has_details = True if self.c.search(value): self.has_differences = True if self.d.search(value): self.has_differences = True def write(self, file, details=True): #stamp = self.data['date'] + ' ' + self.data['time'] stamp = self.data['date'] # construct the name anchor label = '' for c in stamp: if c == ' ': c = '_' if c == ':': continue label = label + c file.write('\n

') file.write('') file.write('\n') if self.data['author'] in authors: self.data['author'] = authors[self.data['author']] file.write(stamp + ' ' + self.data['author']) file.write('') if not details and self.has_details: file.write(' (details)') file.write('

\n') file.write('
\n') file.write('
\n')
		try:
		  for line in self.data['msg']:
			# skip the details unless wanted
			if not details and self.r.search(line): break
			if self.c.search(line): break
			if self.d.search(line): break
			file.write(html_escape(line.encode('utf8')))
		except KeyError:
		  line = '(empty)'
		  file.write(line.encode('utf8'))
		file.write('
\n') file.write('

[') #file.write(string.join(map(string.join, zip(self.data['name'],self.data['revision'])),', ')) #file.write(string.join(self.data['name'])) file.write(string.join(self.data['path'])) file.write(']

\n') file.write('
\n') def write_header(file, details=True): file.write('\n') file.write('\n') file.write('\n') file.write('\n') file.write('') file.write('Ghostscript change history') if details: file.write(' (detailed)') file.write('\n') file.write('\n') file.write('\n') file.write('\n') file.write('\n') file.write('\n') def write_footer(file, details=True): file.write('\n') file.write('\n') # expat hander functions def start_element(name, attrs): #print 'Start element:', name, attrs element.append(name) if name == 'logentry': e = Entry() def end_element(name): #print 'End element:', name element.pop() if name == 'logentry': e.write(details, True) e.write(changes, False) e.reset() def char_data(data): if element[-1] == 'msg': # whitespace is meaningful inside the msg tag # so treat it specially e.addmsg(data) elif element[-1] == 'name' or element[-1] == 'path': # keep an ordered list of these elements item = string.strip(data) # hack off the prefix for mainline paths if item[:10] == '/trunk/gs/': item = item[10:] e.listadd(element[-1], item) else: data = string.strip(data) if data: #print 'Character data:', data e.add(element[-1], data) # global parsing state element = [] e = Entry() # create and hook up the expat instance p = xml.parsers.expat.ParserCreate() p.StartElementHandler = start_element p.EndElementHandler = end_element p.CharacterDataHandler = char_data # open our files if len(sys.argv) != 4: print 'usage: convert the output of svn log -v --xml to html' print sys.argv[0] + ' ' sys.exit(2) input = file(sys.argv[1], "r") changes_fn = sys.argv[2] details_fn = sys.argv[3] changes = file(changes_fn, "wb") details = file(details_fn, "wb") # Try to parse out authors map authors = {} if os.path.exists('AUTHORS'): try: a = open('AUTHORS', 'r') for line in a.readlines(): b = line.split(':') authors[b[0].strip()] = b[1].strip() except: print "Error loading AUTHORS dictionary." # read the input xml and write the output html write_header(changes, False) write_header(details, True) while 1: line = input.readline() if line == '': break p.Parse(line) input.close() write_footer(changes, False) write_footer(details, True) # finished changes.close() details.close()