#!/usr/bin/python # CSS Test Suite Manipulation Library # Initial code by fantasai, joint copyright 2010 W3C and Microsoft # Licensed under BSD 3-Clause: # Define contains vmethod for Template Toolkit from template.stash import list_op @list_op("contains") def list_contains(l, x): return x in l import sys import re import os import codecs from os.path import join, exists, abspath from template import Template import w3ctestlib from Utils import listfiles, escapeToNamedASCII from OutputFormats import ExtensionMap import shutil class Section: def __init__(self, uri, title, numstr): self.uri = uri self.title = title self.numstr = numstr self.tests = [] def __cmp__(self, other): return cmp(self.natsortkey(), other.natsortkey()) def chapterNum(self): return self.numstr.partition('.')[0] def natsortkey(self): chunks = self.numstr.partition('.#')[0].split('.') for index in range(len(chunks)): if chunks[index].isdigit(): # wrap in tuple with '0' to explicitly specify numbers come first chunks[index] = (0, int(chunks[index])) else: chunks[index] = (1, chunks[index]) return (chunks, self.numstr) class Indexer: def __init__(self, suite, sections, suites, flags, splitChapter=False, templatePathList=None, extraData=None, overviewTmplNames=None, overviewCopyExts=('.css', 'htaccess')): """Initialize indexer with TestSuite `suite` toc data file `tocDataPath` and additional template paths in list `templatePathList`. The toc data file should be list of tab-separated records, one per line, of each spec section's uri, number/letter, and title. `splitChapter` selects a single page index if False, chapter indicies if True. `extraData` can be a dictionary whose data gets passed to the templates. `overviewCopyExts` lists file extensions that should be found and copied from the template path into the main build directory. The default value is ['.css', 'htaccess']. `overviewTemplateNames` lists template names that should be processed from the template path into the main build directory. The '.tmpl' extension, if any, is stripped from the output filename. The default value is ['index.htm.tmpl', 'index.xht.tmpl', 'testinfo.data.tmpl'] """ self.suite = suite self.splitChapter = splitChapter self.extraData = extraData self.overviewCopyExtPat = re.compile('.*(%s)$' % '|'.join(overviewCopyExts)) self.overviewTmplNames = overviewTmplNames if overviewTmplNames is not None \ else ['index.htm.tmpl', 'index.xht.tmpl', 'testinfo.data.tmpl', 'implementation-report-TEMPLATE.data.tmpl'] # Initialize template engine self.templatePath = [join(w3ctestlib.__path__[0], 'templates')] if templatePathList: self.templatePath.extend(templatePathList) self.templatePath = [abspath(path) for path in self.templatePath] self.tt = Template({ 'INCLUDE_PATH': self.templatePath, 'ENCODING' : 'utf-8', 'PRE_CHOMP' : 1, 'POST_CHOMP' : 0, }) # Load toc data self.sections = {} for uri, numstr, title in sections: uri = intern(uri.encode('utf-8')) uriKey = intern(self._normalizeScheme(uri)) numstr = escapeToNamedASCII(numstr) title = escapeToNamedASCII(title) if title else None self.sections[uriKey] = Section(uri, title, numstr) self.suites = suites self.flags = flags # Initialize storage self.errors = {} self.contributors = {} self.alltests = [] def _normalizeScheme(self, uri): if (uri and uri.startswith('http:')): return 'https:' + uri[5:] return uri def indexGroup(self, group): for test in group.iterTests(): data = test.getMetadata() if data: # Shallow copy for template output data = dict(data) data['file'] = '/'.join((group.name, test.relpath)) \ if group.name else test.relpath if (data['scripttest']): data['flags'].append(intern('script')) self.alltests.append(data) for uri in data['links']: uri = self._normalizeScheme(uri) uri = uri.replace(self._normalizeScheme(self.suite.draftroot), self._normalizeScheme(self.suite.specroot)) if self.sections.has_key(uri): testlist = self.sections[uri].tests.append(data) for credit in data['credits']: self.contributors[credit[0]] = credit[1] else: self.errors[test.sourcepath] = test.errors def __writeTemplate(self, template, data, outfile): o = self.tt.process(template, data) with open(outfile, 'w') as f: f.write(o.encode('utf-8')) def writeOverview(self, destDir, errorOut=sys.stderr, addTests=[]): """Write format-agnostic pages such as test suite overview pages, test data files, and error reports. Indexed errors are reported to errorOut, which must be either an output handle such as sys.stderr, a tuple of (template filename string, output filename string) or None to suppress error output. `addTests` is a list of additional test paths, relative to the overview root; it is intended for indexing raw tests """ # Set common values data = self.extraData.copy() data['suitetitle'] = self.suite.title data['suite'] = self.suite.name data['specroot'] = self.suite.specroot data['draftroot'] = self.suite.draftroot data['contributors'] = self.contributors data['tests'] = self.alltests data['extmap'] = ExtensionMap({'.xht':'', '.html':'', '.htm':'', '.svg':''}) data['formats'] = self.suite.formats data['addtests'] = addTests data['suites'] = self.suites data['flagInfo'] = self.flags data['formatInfo'] = { 'html4': { 'report': True, 'path': 'html4', 'ext': 'htm', 'filter': 'nonHTML'}, 'html5': { 'report': True, 'path': 'html', 'ext': 'htm', 'filter': 'nonHTML' }, 'xhtml1': { 'report': True, 'path': 'xhtml1', 'ext': 'xht', 'filter': 'HTMLonly' }, 'xhtml1print': { 'report': False, 'path': 'xhtml1print', 'ext': 'xht', 'filter': 'HTMLonly' }, 'svg': { 'report': True, 'path': 'svg', 'ext': 'svg', 'filter': 'HTMLonly' } } # Copy simple copy files for tmplDir in reversed(self.templatePath): files = listfiles(tmplDir) for file in files: if self.overviewCopyExtPat.match(file): shutil.copy(join(tmplDir, file), join(destDir, file)) # Generate indexes for tmpl in self.overviewTmplNames: out = tmpl[0:-5] if tmpl.endswith('.tmpl') else tmpl self.__writeTemplate(tmpl, data, join(destDir, out)) # Report errors if (self.errors): if type(errorOut) is type(('tmpl','out')): data['errors'] = errors self.__writeTemplate(errorOut[0], data, join(destDir, errorOut[1])) else: sys.stdout.flush() for errorLocation in self.errors: print >> errorOut, "Error in %s: %s" % \ (errorLocation, ' '.join([str(error) for error in self.errors[errorLocation]])) def writeIndex(self, format): """Write indices into test suite build output through format `format`. """ # Set common values data = self.extraData.copy() data['suitetitle'] = self.suite.title data['suite'] = self.suite.name data['specroot'] = self.suite.specroot data['draftroot'] = self.suite.draftroot data['indexext'] = format.indexExt data['isXML'] = format.indexExt.startswith('.x') data['formatdir'] = format.formatDirName data['extmap'] = format.extMap data['tests'] = self.alltests data['suites'] = self.suites data['flagInfo'] = self.flags # Generate indices: # Reftest indices self.__writeTemplate('reftest-toc.tmpl', data, format.dest('reftest-toc%s' % format.indexExt)) self.__writeTemplate('reftest.tmpl', data, format.dest('reftest.list')) # Table of Contents sectionlist = sorted(self.sections.values()) if self.splitChapter: # Split sectionlist into chapters chapters = [] lastChapNum = '$' # some nonmatching initial char chap = None for section in sectionlist: if (section.title and (section.chapterNum() != lastChapNum)): lastChapNum = section.chapterNum() chap = section chap.sections = [] chap.testcount = 0 chap.testnames = set() chapters.append(chap) chap.testnames.update([test['name'] for test in section.tests]) chap.testcount = len(chap.testnames) chap.sections.append(section) # Generate main toc data['chapters'] = chapters self.__writeTemplate('chapter-toc.tmpl', data, format.dest('toc%s' % format.indexExt)) del data['chapters'] # Generate chapter tocs for chap in chapters: data['chaptertitle'] = chap.title data['testcount'] = chap.testcount data['sections'] = chap.sections self.__writeTemplate('test-toc.tmpl', data, format.dest('chapter-%s%s' \ % (chap.numstr, format.indexExt))) else: # not splitChapter data['chapters'] = sectionlist self.__writeTemplate('test-toc.tmpl', data, format.dest('toc%s' % format.indexExt)) del data['chapters']