def consolidateFiles(self, xmlFiles): """Given a <files> element, find the directory common to all files and return a 2-tuple with that directory followed by a list of files within that directory. """ files = [] if xmlFiles: for fileTag in XML.getChildElements(xmlFiles): if fileTag.nodeName == 'file': files.append(XML.shallowText(fileTag)) # If we only have one file, return it as the prefix. # This prevents the below regex from deleting the filename # itself, assuming it was a partial filename. if len(files) == 1: return files[0], [] # Start with the prefix found by commonprefix, # then actually make it end with a directory rather than # possibly ending with part of a filename. prefix = re.sub("[^/]*$", "", posixpath.commonprefix(files)) endings = [] for file in files: ending = file[len(prefix):].strip() if ending == '': ending = '.' endings.append(ending) return prefix, endings
def parse(self, message): """Given a string of text in the original CIA commit format, return a <colorText> element representing it as a DOM tree. """ # Initialize our model of the current text format in the original message self.parsedState = ColorState() self.document = XML.createRootNode() # Initialize our stack of (element, ColorState) tuples representing # the state of the XML document being generated. This starts out with # our root element in it. self.elementStack = [ (XML.addElement(self.document, "colorText"), ColorState()) ] # Break up the message into lines, each with its whitespace stripped. # Run our lexical scanner on each line separately, turning it into # a stream of events. Insert <br/> tags between lines. lines = [] for line in message.split("\n"): # Ignore extra whitespace line = line.strip() # Ignore blank lines if line: lines.append(line) for i in xrange(len(lines)): if i != 0: XML.addElement(self.elementStack[-1][0], 'br') self.lex(lines[i]) self.closeTags() return self.document
def component_files(self, element, args): """Format the contents of our <files> tag as a tree with nested lists""" from LibCIA.Web import Template files = XML.dig(args.message.xml, "message", "body", "commit", "files") if not (files and XML.hasChildElements(files)): return [] # First we organize the files into a tree of nested dictionaries. # The dictionary we ultimately have FileTree render maps each node # (file or directory) to a dictionary of its contents. The keys # in these dictionaries can be any Nouvelle-renderable object # produced by format_file. # # As a first step, we build a dictionary mapping path segment to # [fileTag, children] lists. We then create a visual representation # of each fileTag and generate the final dictionary. fileTree = {} for fileTag in XML.getChildElements(files): if fileTag.nodeName == 'file': # Separate the file into path segments and walk into our tree node = [None, fileTree] for segment in XML.shallowText(fileTag).split('/'): if segment: node = node[1].setdefault(segment, [None, {}]) # The leaf node owns this fileTag node[0] = fileTag return [Template.FileTree(self.format_file_tree(fileTree))]
def format(self, args): # Format each package inside each result set packages = [] for results in XML.getChildElements(XML.dig(args.message.xml, "message", "body", "builder")): if results.nodeName == 'results': for package in XML.getChildElements(results): if package.nodeName == 'package': packages.append(self.format_package(package)) return self.joinMessage(args.message, packages)
def format(self, args): if not args.input: return project = XML.dig(args.message.xml, "message", "source", "project") if project: from LibCIA.IRC.Formatting import format prefix = format("%s:" % XML.shallowText(project), 'bold') + " " return "\n".join([prefix + line for line in args.input.split("\n")]) else: return args.input
def format(self, args): if not args.input: return project = XML.dig(args.message.xml, "message", "source", "project") if project: from LibCIA.IRC.Formatting import format prefix = format("%s:" % XML.shallowText(project), 'bold') + " " return "\n".join( [prefix + line for line in args.input.split("\n")]) else: return args.input
def format_generator(self, gen): """Format the information contained in this message's <generator> tag""" name = XML.digValue(gen, str, "name") url = XML.digValue(gen, str, "url") version = XML.digValue(gen, str, "version") if url: name = tag('a', href=url)[ name ] items = ["Generated by ", Template.value[ name ]] if version: items.extend([" version ", version]) return items
def format_generator(self, gen): """Format the information contained in this message's <generator> tag""" name = XML.digValue(gen, str, "name") url = XML.digValue(gen, str, "url") version = XML.digValue(gen, str, "version") if url: name = tag("a", href=url)[name] items = ["Generated by ", Template.value[name]] if version: items.extend([" version ", version]) return items
def render_rows(self, context): if not self.message: return [] rows = [] timestamp = XML.dig(self.message.xml, "message", "timestamp") if timestamp: rows.append(self.format_timestamp(timestamp)) generator = XML.dig(self.message.xml, "message", "generator") if generator: rows.append(self.format_generator(generator)) return rows
def getSvnRevision(self): """Return the current Subversion repository revision, or None if we're not in an svn working copy or it can't be parsed. """ try: entries = XML.parseString(open(".svn/entries").read()).documentElement highestRev = 0 for tag in XML.getChildElements(entries): if tag.nodeName == 'entry': rev = tag.getAttributeNS(None, 'committed-rev') if rev and rev > highestRev: highestRev = rev return highestRev except: return None
def syncFromServer(self): """Update this Bot from the RPC server, if necessary. Right now the only task this performs is to store the server's ruleset if filterMode is 'unknown'. """ if self.filter_mode != FILTER.UNKNOWN: return ruleset = self._loadRuleset() if not ruleset: # If there's no ruleset, mark the bot as inactive. self.filter_mode = FILTER.INACTIVE self.save() return # Parse the ruleset using LibCIA's XML library from LibCIA import XML dom = XML.parseString(ruleset) # XXX: We should try to reduce the ruleset to one of # the other FILTER.* modes if possible. For now, # we'll always import existing rulesets as # FILTER.CUSTOM. # Flatten the contents of the <ruleset> element, clean up # the resulting text, and save that as a custom filter. text = ''.join([n.toxml() for n in dom.documentElement.childNodes]) self.filter_mode = FILTER.CUSTOM self.custom_ruleset = clean_up_text(text) self.save()
def render_message(self, context): if not self.message: context["request"].setResponseCode(404) return self.notFoundMessage # Try to format it using several media, in order of decreasing preference. # The 'xhtml-long' formatter lets messages define a special formatter to # use when an entire page is devoted to their one message, possibly showing # it in greater detail. 'xhtml' is the formatter most messages should have. # 'plaintext' is a nice fallback. # # This default list of media to try can be overridden with an argument in our URL. if "media" in context["args"]: mediaList = context["args"]["media"][0].split() else: mediaList = ("xhtml-long", "xhtml", "plaintext") for medium in mediaList: try: formatted = Formatters.getFactory().findMedium(medium, self.message).formatMessage(self.message) except Message.NoFormatterError: continue return formatted # Still no luck? Display a warning message and a pretty-printed XML tree return [tag("h1")["No formatter available"], XML.htmlPrettyPrint(self.message.xml)]
def format_results(self, package): """Given a package, returns a formatted representation of all results for that package""" results = [] for element in XML.getChildElements(package): f = getattr(self, 'result_' + element.nodeName, None) if f: results.append(f(element))
def param_noColor(self, tag): """The <noColor> parameter disables colors. This is equivalent to a <format> parameter with CommitFormatter's default component tree. """ self.componentTree = XML.parseString( CommitFormatter.defaultComponentTree).documentElement
def param_noColor(self, tag): """The <noColor> parameter disables colors. This is equivalent to a <format> parameter with CommitFormatter's default component tree. """ self.componentTree = XML.parseString(CommitFormatter.defaultComponentTree ).documentElement
def render_message(self, context): if not self.message: context['request'].setResponseCode(404) return self.notFoundMessage # Try to format it using several media, in order of decreasing preference. # The 'xhtml-long' formatter lets messages define a special formatter to # use when an entire page is devoted to their one message, possibly showing # it in greater detail. 'xhtml' is the formatter most messages should have. # 'plaintext' is a nice fallback. # # This default list of media to try can be overridden with an argument in our URL. if 'media' in context['args']: mediaList = context['args']['media'][0].split() else: mediaList = ('xhtml-long', 'xhtml', 'plaintext') for medium in mediaList: try: formatted = Formatters.getFactory().findMedium( medium, self.message).formatMessage(self.message) except Message.NoFormatterError: continue return formatted # Still no luck? Display a warning message and a pretty-printed XML tree return [ tag('h1')[ "No formatter available" ], XML.htmlPrettyPrint(self.message.xml), ]
def parseColorElement(xml): """Given a <color> element, return the corresponding list of color code names""" codes = [] bg = xml.getAttributeNS(None, 'bg') fg = xml.getAttributeNS(None, 'fg') if bg: if bg in ColorText.allowedColors: codes.append(bg) codes.append('reverse') else: raise XML.XMLValidityError("%r is not a color" % bg) if fg: if fg in ColorText.allowedColors: codes.append(fg) else: raise XML.XMLValidityError("%r is not a color" % fg) return codes
def render(self, request): xml = self.target.messages.getMessageById(self.id) if xml: request.setHeader('content-type', 'text/xml') request.write(unicode(XML.toString(xml)).encode('utf-8')) request.finish() else: request.write(error.NoResource("Message not found").render(request)) request.finish() return ""
def render(self, request): xml = self.target.messages.getMessageById(self.id) if xml: request.setHeader("content-type", "text/xml") request.write(unicode(XML.toString(xml)).encode("utf-8")) request.finish() else: request.write(error.NoResource("Message not found").render(request)) request.finish() return ""
class Message: serializer = Serializer() formatter_factory = OldFormatters.getFactory() def __init__(self, (id, xml)): self.id = id self.hex_id = "%x" % id self.oldmsg = OldMessage(xml) self.timestamp = datetime.datetime.fromtimestamp( XML.digValue(self.oldmsg.xml, float, "message", "timestamp")) self.formatter = self.formatter_factory.findMedium( 'xhtml', self.oldmsg) self.is_commit = isinstance(self.formatter, CommitFormatter) if self.is_commit: for shortcut, path in XML.pathShortcuts.items(): doc = XML.XPath(path).queryObject(self.oldmsg) if doc: setattr(self, shortcut, XML.shallowText(doc[0]))
def component_files(self, element, args): """Break up our list of files into a common prefix and a sensibly-sized list of filenames after that prefix. """ files = XML.dig(args.message.xml, "message", "body", "commit", "files") if not (files and XML.hasChildElements(files)): return [Message.MarkAsHidden()] prefix, endings = self.consolidateFiles(files) endingStr = " ".join(endings) if len(endingStr) > self.filesWidthLimit: # If the full file list is too long, give a file summary instead endingStr = self.summarizeFiles(endings) if prefix.startswith('/'): prefix = prefix[1:] if endingStr: return ["%s (%s)" % (prefix, endingStr)] else: return [prefix]
def joinMessage(self, message, packages): content = [] branch = XML.digValue(message.xml, str, "message", "source", "branch") if branch: content.append(Nouvelle.tag('strong')[ self.format_branch(branch.strip()) ]) for package in packages: if content: content.append(Nouvelle.tag('br')) content.append(package) return content
def joinMessage(self, message, packages): """Join the results for each package into a final result""" content = "builder" branch = XML.digValue(message.xml, str, "message", "source", "branch") if branch: content += " " + self.format_branch(branch.strip()) # If we have only one package, put it on the same line as the heading if len(packages) <= 1: content += ": " + packages[0] else: content += "\n" + "\n".join(packages) return content
def extractSummary(element, widthLimit=80): """Extract all text from the given XML element, remove extra whitespace, and truncate it to no longer than the given width. """ # Extract all text, eating extra whitespace text = re.sub("\s+", " ", XML.allText(element)).strip() # Use wrapLine to cleanly break it if possible, but # truncate it if necessary- wrapLine will not break words in # half if they are longer than the wrap width. lines = wrapLine(text, widthLimit) if lines: summary = lines[0][:widthLimit] if len(summary) < len(text): summary += "..." return summary
def render_item(self, context, id, content): url = Link.MessageLink(self.target, id).getURL(context) m = Message.Message(content) tags = [ tag('link')[ url ], tag('dc:date')[ TimeUtil.formatDateISO8601(XML.digValue(m.xml, int, "message", "timestamp")) ], tag('description')[ quote(self.formatMessage(m)) ], ] # Generate a title if we can, but if we can't don't worry too much try: tags.append(tag('title')[ Formatters.getFactory().findMedium('title', m).formatMessage(m) ]) except Message.NoFormatterError: pass return tag('item', **{'rdf:about': url})[tags]
def messageToItemContent(self, context, m, id): """Render an XML message as the content of an RSS <item>""" url = Link.MessageLink(self.target, id).getURL(context) tags = [ tag('pubDate')[ TimeUtil.formatDateRFC822(XML.digValue(m.xml, int, "message", "timestamp")) ], tag('guid')[url], tag('link')[url], tag('description')[ quote(self.formatMessage(m)) ], ] # Generate a title if we can, but if we can't don't worry too much try: tags.append(tag('title')[ Formatters.getFactory().findMedium('title', m).formatMessage(m) ]) except Message.NoFormatterError: pass return tags
def pushTag(self, name, attributes={}, stateChanges={}): """Add a new element to the elementStack, placed at the end of the children list for the tag currently at the top of the stack. name: The name of the new element attributes: A dict of attributes to set on the new element stateChanges: A dict of attributes to change in the new tag's state """ oldTag, oldState = self.elementStack[-1] newTag = XML.addElement(self.elementStack[-1][0], name) for key, value in attributes.iteritems(): newTag.setAttributeNS(None, key, value) newState = copy.deepcopy(oldState) newState.__dict__.update(stateChanges) self.elementStack.append((newTag, newState))
def getNormalizedLog(xml, tabWidth=8): """Given the DOM node for a <log> tag, return a list of text lines with whitespace normalized appropriately. This strips all whitespace from the right side, and homogeneously strips whitespace from the left side as much as possible. Leading and trailing blank lines are removed, but internal blank lines are not. """ if not xml: return [] lines = [] maxLeftStrip = None for line in XML.shallowText(xml).split("\n"): # Expand tabs and strip righthand whitespace line = line.replace("\t", " "*tabWidth).rstrip() strippedLine = line.lstrip() # Blank lines don't count in determining the left strip amount if strippedLine: # Determine how much we can strip from the left side leftStrip = len(line) - len(strippedLine) # Determine the maximum amount of space we can strip # from the left side homogeneously across the whole text if maxLeftStrip is None or leftStrip < maxLeftStrip: maxLeftStrip = leftStrip # Skip leading blank lines if lines or strippedLine: lines.append(line) # Remove trailing blank lines while lines and not lines[-1].strip(): del lines[-1] # Homogeneous left strip if maxLeftStrip is None: return lines else: return [line[maxLeftStrip:] for line in lines]
def component_log(self, element, args): log = XML.dig(args.message.xml, "message", "body", "commit", "log") if not log: return [Message.MarkAsHidden()] if self.crunchWhitespace: inputLines = [Util.getCrunchedLog(log)] else: inputLines = Util.getNormalizedLog(log) # Break the log string into wrapped lines lines = [] for line in inputLines: # Ignore blank lines if not line: continue # Wrap long lines if self.widthLimit and len(line) > self.widthLimit: lines.extend(Util.wrapLine(line, self.wrapWidth)) else: lines.append(line) # If our lineLimit is 1, don't bother starting long logs on the # next line since there will be no long logs. Instead of the # long (log message trimmed), just add an ellipsis. if self.lineLimit == 1: if len(lines) > 1: lines[0] += ' ...' del lines[1:] # Multiline logs shouldn't start on the same line as the metadata elif len(lines) > 1: lines.insert(0, '') # Truncate long log messages if we have a limit if self.lineLimit and len(lines) > self.lineLimit + 1: lines[0] = "(log message trimmed)" del lines[self.lineLimit + 1:] # Reassemble the log message and send it to the default formatter return ["\n".join(lines)]
def getNormalizedLog(xml, tabWidth=8): """Given the DOM node for a <log> tag, return a list of text lines with whitespace normalized appropriately. This strips all whitespace from the right side, and homogeneously strips whitespace from the left side as much as possible. Leading and trailing blank lines are removed, but internal blank lines are not. """ if not xml: return [] lines = [] maxLeftStrip = None for line in XML.shallowText(xml).split("\n"): # Expand tabs and strip righthand whitespace line = line.replace("\t", " " * tabWidth).rstrip() strippedLine = line.lstrip() # Blank lines don't count in determining the left strip amount if strippedLine: # Determine how much we can strip from the left side leftStrip = len(line) - len(strippedLine) # Determine the maximum amount of space we can strip # from the left side homogeneously across the whole text if maxLeftStrip is None or leftStrip < maxLeftStrip: maxLeftStrip = leftStrip # Skip leading blank lines if lines or strippedLine: lines.append(line) # Remove trailing blank lines while lines and not lines[-1].strip(): del lines[-1] # Homogeneous left strip if maxLeftStrip is None: return lines else: return [line[maxLeftStrip:] for line in lines]
def getValue(self, message): try: return XML.digValue(message.xml, int, "message", "timestamp") except ValueError: return None
def param_widthLimit(self, tag): self.widthLimit = int(XML.shallowText(tag)) if self.wrapWidth > self.widthLimit: self.wrapWidth = self.widthLimit
def loadParametersFrom(self, xml): self.formattingCode = XML.shallowText(xml).strip()
def getCrunchedLog(xml): """Given the DOM node for a <log> tag, return the log as a string with all groups of one or more whitespace characters replaced with a single space. """ return re.sub("\s+", " ", XML.shallowText(xml)).strip()
def xmlrpc_getLatestMessages(self, path, limit=None): """Return 'limit' latest messages delivered to this stats target, or all available recent messages if 'limit' isn't specified. """ return [(id, XML.toString(doc)) for id, doc in StatsTarget(path).messages.getLatest(limit)]
def formatItem(self, content): # Convert the root node, not the document- we don't want to # be outputting another XML declaration inside our larger document. return xml(XML.toString(content.childNodes[0]).encode('utf8'))
def param_wrapWidth(self, tag): self.wrapWidth = int(XML.shallowText(tag))
def param_filesWidthLimit(self, tag): self.filesWidthLimit = int(XML.shallowText(tag))
def getValue(self, message): project = XML.dig(message.xml, "message", "source", "project") if project: return XML.shallowText(project)
def param_lineLimit(self, tag): self.lineLimit = int(XML.shallowText(tag))
def format(self, args): colorText = XML.dig(args.message.xml, "message", "body", "colorText") if self.color: return self.formatter.parse(colorText) else: return XML.allText(colorText)
def format(self, args): colorText = XML.dig(args.message.xml, "message", "body", "colorText") return self.Parser(colorText).result
def format(self, args): return Util.extractSummary(XML.dig(args.message.xml, "message", "body", "colorText"))
def format_timestamp(self, stamp): return ["Received ", Template.value[TimeUtil.formatRelativeDate(int(XML.shallowText(stamp)))]]