| OLD | NEW |
| (Empty) |
| 1 | |
| 2 from zope.interface import implements | |
| 3 from twisted.python import components | |
| 4 from twisted.spread import pb | |
| 5 from twisted.web import html, server | |
| 6 from twisted.web.resource import Resource | |
| 7 from twisted.web.error import NoResource | |
| 8 | |
| 9 from buildbot import interfaces | |
| 10 from buildbot.status import builder | |
| 11 from buildbot.status.web.base import IHTMLLog, HtmlResource | |
| 12 from buildbot.status.web.ansi2html import Ansi2HTML | |
| 13 | |
| 14 | |
| 15 textlog_stylesheet = """ | |
| 16 <style type="text/css"> | |
| 17 div.data { | |
| 18 font-family: "Courier New", courier, monotype; | |
| 19 } | |
| 20 span.stdout { | |
| 21 font-family: "Courier New", courier, monotype; | |
| 22 } | |
| 23 span.stderr { | |
| 24 font-family: "Courier New", courier, monotype; | |
| 25 color: red; | |
| 26 } | |
| 27 span.header { | |
| 28 font-family: "Courier New", courier, monotype; | |
| 29 color: blue; | |
| 30 } | |
| 31 </style> | |
| 32 """ | |
| 33 | |
| 34 class ChunkConsumer: | |
| 35 implements(interfaces.IStatusLogConsumer) | |
| 36 | |
| 37 def __init__(self, original, textlog): | |
| 38 self.original = original | |
| 39 self.textlog = textlog | |
| 40 def registerProducer(self, producer, streaming): | |
| 41 self.producer = producer | |
| 42 self.original.registerProducer(producer, streaming) | |
| 43 def unregisterProducer(self): | |
| 44 self.original.unregisterProducer() | |
| 45 def writeChunk(self, chunk): | |
| 46 formatted = self.textlog.content([chunk]) | |
| 47 try: | |
| 48 if isinstance(formatted, unicode): | |
| 49 formatted = formatted.encode('utf-8') | |
| 50 self.original.write(formatted) | |
| 51 except pb.DeadReferenceError: | |
| 52 self.producing.stopProducing() | |
| 53 def finish(self): | |
| 54 self.textlog.finished() | |
| 55 | |
| 56 | |
| 57 # /builders/$builder/builds/$buildnum/steps/$stepname/logs/$logname | |
| 58 class TextLog(Resource): | |
| 59 # a new instance of this Resource is created for each client who views | |
| 60 # it, so we can afford to track the request in the Resource. | |
| 61 implements(IHTMLLog) | |
| 62 | |
| 63 printAs = "html" | |
| 64 subscribed = False | |
| 65 | |
| 66 def __init__(self, original): | |
| 67 Resource.__init__(self) | |
| 68 self.original = original | |
| 69 | |
| 70 def getChild(self, path, req): | |
| 71 if path == "ansi": | |
| 72 self.ansiParser = Ansi2HTML() | |
| 73 | |
| 74 if path == "text" or path == "ansi": | |
| 75 self.printAs = path | |
| 76 return self | |
| 77 | |
| 78 return HtmlResource.getChild(self, path, req) | |
| 79 | |
| 80 def htmlHeader(self, request): | |
| 81 title = "Log File contents" | |
| 82 data = "<html>\n<head><title>" + title + "</title>\n" | |
| 83 data += textlog_stylesheet | |
| 84 data += "</head>\n" | |
| 85 data += "<body vlink=\"#800080\">\n" | |
| 86 texturl = request.childLink("text") | |
| 87 data += '<a href="%s">(view as text)</a><br />\n' % texturl | |
| 88 ansiurl = request.childLink("ansi") | |
| 89 data += '<a href="%s">(view as ansi)</a><br />\n' % ansiurl | |
| 90 data += "<pre>\n" | |
| 91 return data | |
| 92 | |
| 93 def content(self, entries): | |
| 94 spanfmt = '<span class="%s">%s</span>' | |
| 95 data = "" | |
| 96 for type, entry in entries: | |
| 97 if type >= len(builder.ChunkTypes) or type < 0: | |
| 98 # non-std channel, don't display | |
| 99 continue | |
| 100 if self.printAs == "text": | |
| 101 if type != builder.HEADER: | |
| 102 data += entry | |
| 103 elif self.printAs == "ansi": | |
| 104 if type != builder.HEADER: | |
| 105 data += self.ansiParser.parseBlock(entry) | |
| 106 else: | |
| 107 data += spanfmt % (builder.ChunkTypes[type], | |
| 108 html.escape(entry)) | |
| 109 return data | |
| 110 | |
| 111 def htmlFooter(self): | |
| 112 data = "</pre>\n" | |
| 113 data += "</body></html>\n" | |
| 114 return data | |
| 115 | |
| 116 def render_HEAD(self, request): | |
| 117 if self.printAs == "text": | |
| 118 request.setHeader("content-type", "text/plain") | |
| 119 else: | |
| 120 request.setHeader("content-type", "text/html") | |
| 121 | |
| 122 # vague approximation, ignores markup | |
| 123 request.setHeader("content-length", self.original.length) | |
| 124 return '' | |
| 125 | |
| 126 def render_GET(self, req): | |
| 127 self.req = req | |
| 128 | |
| 129 if self.printAs == "text": | |
| 130 req.setHeader("content-type", "text/plain") | |
| 131 else: | |
| 132 req.setHeader("content-type", "text/html") | |
| 133 | |
| 134 if self.printAs == "html": | |
| 135 req.write(self.htmlHeader(req)) | |
| 136 if self.printAs == "ansi": | |
| 137 req.write(self.ansiParser.printHtmlHeader("Log File Contents")) | |
| 138 req.write(self.ansiParser.printHeader()) | |
| 139 | |
| 140 self.original.subscribeConsumer(ChunkConsumer(req, self)) | |
| 141 return server.NOT_DONE_YET | |
| 142 | |
| 143 def finished(self): | |
| 144 if not self.req: | |
| 145 return | |
| 146 try: | |
| 147 if self.printAs == "html": | |
| 148 self.req.write(self.htmlFooter()) | |
| 149 if self.printAs == "ansi": | |
| 150 self.req.write(self.ansiParser.printFooter()) | |
| 151 self.req.write(self.ansiParser.printHtmlFooter()) | |
| 152 self.req.finish() | |
| 153 except pb.DeadReferenceError: | |
| 154 pass | |
| 155 # break the cycle, the Request's .notifications list includes the | |
| 156 # Deferred (from req.notifyFinish) that's pointing at us. | |
| 157 self.req = None | |
| 158 | |
| 159 components.registerAdapter(TextLog, interfaces.IStatusLog, IHTMLLog) | |
| 160 | |
| 161 | |
| 162 class HTMLLog(Resource): | |
| 163 implements(IHTMLLog) | |
| 164 | |
| 165 def __init__(self, original): | |
| 166 Resource.__init__(self) | |
| 167 self.original = original | |
| 168 | |
| 169 def render(self, request): | |
| 170 request.setHeader("content-type", "text/html") | |
| 171 return self.original.html | |
| 172 | |
| 173 components.registerAdapter(HTMLLog, builder.HTMLLogFile, IHTMLLog) | |
| 174 | |
| 175 | |
| 176 class LogsResource(HtmlResource): | |
| 177 addSlash = True | |
| 178 | |
| 179 def __init__(self, step_status): | |
| 180 HtmlResource.__init__(self) | |
| 181 self.step_status = step_status | |
| 182 | |
| 183 def getChild(self, path, req): | |
| 184 for log in self.step_status.getLogs(): | |
| 185 if path == log.getName(): | |
| 186 if log.hasContents(): | |
| 187 return IHTMLLog(interfaces.IStatusLog(log)) | |
| 188 return NoResource("Empty Log '%s'" % path) | |
| 189 return HtmlResource.getChild(self, path, req) | |
| OLD | NEW |