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 |